@noatgnu/cupcake-vanilla 1.4.0 → 1.4.2

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.
@@ -723,10 +723,10 @@ class MetadataTableService extends BaseApiService {
723
723
  formData.append('normalize_ontology', String(options.normalizeOntology ?? true));
724
724
  return this.post(`${this.apiUrl}/metadata-tables/${tableId}/commit_column_override/`, formData);
725
725
  }
726
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
727
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
727
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
728
728
  }
729
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableService, decorators: [{
729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableService, decorators: [{
730
730
  type: Injectable,
731
731
  args: [{
732
732
  providedIn: 'root'
@@ -834,10 +834,10 @@ class MetadataColumnService extends BaseApiService {
834
834
  const httpParams = this.buildHttpParams(params);
835
835
  return this.get(`${this.apiUrl}/metadata-columns/${id}/history/`, { params: httpParams });
836
836
  }
837
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
838
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
837
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
838
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
839
839
  }
840
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnService, decorators: [{
840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnService, decorators: [{
841
841
  type: Injectable,
842
842
  args: [{
843
843
  providedIn: 'root'
@@ -888,10 +888,10 @@ class SamplePoolService extends BaseApiService {
888
888
  getMetadataColumns(id) {
889
889
  return this.get(`${this.apiUrl}/sample-pools/${id}/metadata_columns/`);
890
890
  }
891
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
892
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
891
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
892
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
893
893
  }
894
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolService, decorators: [{
894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolService, decorators: [{
895
895
  type: Injectable,
896
896
  args: [{
897
897
  providedIn: 'root'
@@ -1047,10 +1047,10 @@ class MetadataTableTemplateService extends BaseApiService {
1047
1047
  syncFromSchemas(id, request) {
1048
1048
  return this.post(`${this.apiUrl}/metadata-table-templates/${id}/sync_from_schemas/`, request || {});
1049
1049
  }
1050
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1051
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
1050
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1051
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
1052
1052
  }
1053
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
1053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
1054
1054
  type: Injectable,
1055
1055
  args: [{
1056
1056
  providedIn: 'root'
@@ -1095,10 +1095,10 @@ class FavouriteMetadataOptionService extends BaseApiService {
1095
1095
  deleteFavouriteMetadataOption(id) {
1096
1096
  return this.delete(`${this.apiUrl}/favourite-options/${id}/`);
1097
1097
  }
1098
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1099
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
1098
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1099
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
1100
1100
  }
1101
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
1101
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
1102
1102
  type: Injectable,
1103
1103
  args: [{
1104
1104
  providedIn: 'root'
@@ -1203,10 +1203,10 @@ class MetadataColumnTemplateService extends BaseApiService {
1203
1203
  return this.get(`${this.apiUrl}/column-templates/`, { params: httpParams })
1204
1204
  .pipe(map(response => response.results || []));
1205
1205
  }
1206
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1207
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1206
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1207
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1208
1208
  }
1209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1209
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1210
1210
  type: Injectable,
1211
1211
  args: [{
1212
1212
  providedIn: 'root'
@@ -1245,10 +1245,10 @@ class MetadataColumnTemplateShareService extends BaseApiService {
1245
1245
  deleteMetadataColumnTemplateShare(id) {
1246
1246
  return this.delete(`${this.apiUrl}/template-shares/${id}/`);
1247
1247
  }
1248
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1249
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1248
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1249
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1250
1250
  }
1251
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1252
1252
  type: Injectable,
1253
1253
  args: [{
1254
1254
  providedIn: 'root'
@@ -1301,10 +1301,10 @@ class MetadataManagementService extends BaseApiService {
1301
1301
  exportSdrfFile(request) {
1302
1302
  return this.post(`${this.apiUrl}/metadata-management/export_sdrf_file/`, request, { responseType: 'blob' });
1303
1303
  }
1304
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1305
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1304
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1305
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1306
1306
  }
1307
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataManagementService, decorators: [{
1307
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataManagementService, decorators: [{
1308
1308
  type: Injectable,
1309
1309
  args: [{
1310
1310
  providedIn: 'root'
@@ -1324,10 +1324,10 @@ class Websocket extends WebSocketService {
1324
1324
  getLabGroupUpdates() {
1325
1325
  return this.filterMessages('lab_group.update');
1326
1326
  }
1327
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: Websocket, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1328
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: Websocket, providedIn: 'root' });
1327
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: Websocket, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1328
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: Websocket, providedIn: 'root' });
1329
1329
  }
1330
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: Websocket, decorators: [{
1330
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: Websocket, decorators: [{
1331
1331
  type: Injectable,
1332
1332
  args: [{
1333
1333
  providedIn: 'root'
@@ -1584,10 +1584,10 @@ class NotificationService extends NotificationService$1 {
1584
1584
  createVanillaNotification(type, title, message, level = 'info', autoClose = true) {
1585
1585
  this.createLocalNotification(type, title, message, level, autoClose);
1586
1586
  }
1587
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1588
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, providedIn: 'root' });
1587
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1588
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: NotificationService, providedIn: 'root' });
1589
1589
  }
1590
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, decorators: [{
1590
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: NotificationService, decorators: [{
1591
1591
  type: Injectable,
1592
1592
  args: [{
1593
1593
  providedIn: 'root'
@@ -1655,10 +1655,10 @@ class OntologySearchService extends BaseApiService {
1655
1655
  limit
1656
1656
  });
1657
1657
  }
1658
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1659
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1659
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1660
1660
  }
1661
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OntologySearchService, decorators: [{
1661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OntologySearchService, decorators: [{
1662
1662
  type: Injectable,
1663
1663
  args: [{
1664
1664
  providedIn: 'root'
@@ -1697,10 +1697,10 @@ class SchemaService extends BaseApiService {
1697
1697
  syncBuiltin() {
1698
1698
  return this.post(`${this.apiUrl}/schemas/sync_builtin/`, {});
1699
1699
  }
1700
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1701
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SchemaService, providedIn: 'root' });
1700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1701
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SchemaService, providedIn: 'root' });
1702
1702
  }
1703
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SchemaService, decorators: [{
1703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SchemaService, decorators: [{
1704
1704
  type: Injectable,
1705
1705
  args: [{
1706
1706
  providedIn: 'root'
@@ -1791,10 +1791,10 @@ class SDRFDefaultsService extends BaseApiService {
1791
1791
  getQuickValues() {
1792
1792
  return this.get(`${this.apiUrl}/sdrf-defaults/quick_values/`);
1793
1793
  }
1794
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1795
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1794
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1795
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1796
1796
  }
1797
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1797
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1798
1798
  type: Injectable,
1799
1799
  args: [{
1800
1800
  providedIn: 'root'
@@ -2047,10 +2047,10 @@ class OfficialColumnCacheService {
2047
2047
  this.clear();
2048
2048
  return this.initialize();
2049
2049
  }
2050
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OfficialColumnCacheService, deps: [{ token: MetadataColumnTemplateService }], target: i0.ɵɵFactoryTarget.Injectable });
2051
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OfficialColumnCacheService, providedIn: 'root' });
2050
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OfficialColumnCacheService, deps: [{ token: MetadataColumnTemplateService }], target: i0.ɵɵFactoryTarget.Injectable });
2051
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OfficialColumnCacheService, providedIn: 'root' });
2052
2052
  }
2053
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OfficialColumnCacheService, decorators: [{
2053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OfficialColumnCacheService, decorators: [{
2054
2054
  type: Injectable,
2055
2055
  args: [{
2056
2056
  providedIn: 'root',
@@ -2090,10 +2090,10 @@ class AsyncTaskService extends BaseApiService {
2090
2090
  const httpParams = this.buildHttpParams({ token });
2091
2091
  return this.get(`${this.apiUrl}/async-tasks/${id}/download/`, { params: httpParams, responseType: 'blob' });
2092
2092
  }
2093
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2094
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
2093
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2094
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
2095
2095
  }
2096
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskService, decorators: [{
2096
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskService, decorators: [{
2097
2097
  type: Injectable,
2098
2098
  args: [{
2099
2099
  providedIn: 'root'
@@ -2113,10 +2113,10 @@ class AsyncExportService extends BaseApiService {
2113
2113
  multipleExcelTemplates(request) {
2114
2114
  return this.post(`${this.apiUrl}/async-export/multiple_excel_templates/`, request);
2115
2115
  }
2116
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2117
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
2116
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2117
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
2118
2118
  }
2119
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncExportService, decorators: [{
2119
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncExportService, decorators: [{
2120
2120
  type: Injectable,
2121
2121
  args: [{
2122
2122
  providedIn: 'root'
@@ -2378,10 +2378,10 @@ class ChunkedUploadService extends BaseApiService {
2378
2378
  uploadNextChunk();
2379
2379
  });
2380
2380
  }
2381
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2382
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
2381
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2382
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
2383
2383
  }
2384
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedUploadService, decorators: [{
2384
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedUploadService, decorators: [{
2385
2385
  type: Injectable,
2386
2386
  args: [{
2387
2387
  providedIn: 'root'
@@ -2418,10 +2418,10 @@ class AsyncImportService extends BaseApiService {
2418
2418
  result: result
2419
2419
  })));
2420
2420
  }
2421
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncImportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2422
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
2421
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncImportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2422
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
2423
2423
  }
2424
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncImportService, decorators: [{
2424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncImportService, decorators: [{
2425
2425
  type: Injectable,
2426
2426
  args: [{
2427
2427
  providedIn: 'root'
@@ -2454,10 +2454,10 @@ class AsyncValidationService extends BaseApiService {
2454
2454
  message: response.message ?? 'SDRF validation task queued successfully'
2455
2455
  })));
2456
2456
  }
2457
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2458
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2457
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2458
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2459
2459
  }
2460
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncValidationService, decorators: [{
2460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncValidationService, decorators: [{
2461
2461
  type: Injectable,
2462
2462
  args: [{
2463
2463
  providedIn: 'root'
@@ -2641,10 +2641,10 @@ class ChunkedImportService extends BaseApiService {
2641
2641
  };
2642
2642
  return this.post(`${this.apiUrl}/async-import/chunked_file/`, payload);
2643
2643
  }
2644
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedImportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2645
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2644
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedImportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2645
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2646
2646
  }
2647
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChunkedImportService, decorators: [{
2647
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ChunkedImportService, decorators: [{
2648
2648
  type: Injectable,
2649
2649
  args: [{
2650
2650
  providedIn: 'root'
@@ -2658,7 +2658,8 @@ class SdrfSyntaxService {
2658
2658
  cleavage: /^comment\[cleavage agent details\]$/i,
2659
2659
  spiked_compound: /^characteristics\[spiked compound\]$/i,
2660
2660
  pooled_sample: /^characteristics\[pooled sample\]$/i,
2661
- synthetic_peptide: /^characteristics\[synthetic peptide\]$/i
2661
+ synthetic_peptide: /^characteristics\[synthetic peptide\]$/i,
2662
+ mz_range: /^comment\[ms\d?\s*scan\s*range\]$/i,
2662
2663
  };
2663
2664
  MODIFICATION_KEYS = ['NT', 'AC', 'CF', 'MT', 'PP', 'TA', 'MM', 'TS'];
2664
2665
  CLEAVAGE_KEYS = ['NT', 'AC', 'CS'];
@@ -2683,6 +2684,12 @@ class SdrfSyntaxService {
2683
2684
  if (this.SPECIAL_COLUMN_PATTERNS.spiked_compound.test(name) || this.SPECIAL_COLUMN_PATTERNS.spiked_compound.test(type)) {
2684
2685
  return 'spiked_compound';
2685
2686
  }
2687
+ if (this.SPECIAL_COLUMN_PATTERNS.pooled_sample.test(name) || this.SPECIAL_COLUMN_PATTERNS.pooled_sample.test(type)) {
2688
+ return 'pooled_sample';
2689
+ }
2690
+ if (this.SPECIAL_COLUMN_PATTERNS.mz_range.test(name) || this.SPECIAL_COLUMN_PATTERNS.mz_range.test(type)) {
2691
+ return 'mz_range';
2692
+ }
2686
2693
  return null;
2687
2694
  }
2688
2695
  /**
@@ -2702,12 +2709,17 @@ class SdrfSyntaxService {
2702
2709
  return this.parseKeyValuePairs(value, this.CLEAVAGE_KEYS);
2703
2710
  case 'spiked_compound':
2704
2711
  return this.parseKeyValuePairs(value, this.SPIKED_COMPOUND_KEYS);
2712
+ case 'pooled_sample':
2713
+ return this.parsePooledSample(value);
2714
+ case 'mz_range':
2715
+ return this.parseMzRange(value);
2716
+ case 'synthetic_peptide':
2717
+ return { value };
2705
2718
  default:
2706
2719
  throw new Error(`Unknown syntax type: ${syntaxType}`);
2707
2720
  }
2708
2721
  }
2709
2722
  catch (error) {
2710
- console.error(`Error parsing ${syntaxType} value:`, error);
2711
2723
  return null;
2712
2724
  }
2713
2725
  }
@@ -2726,12 +2738,17 @@ class SdrfSyntaxService {
2726
2738
  case 'cleavage':
2727
2739
  case 'spiked_compound':
2728
2740
  return this.formatKeyValuePairs(data);
2741
+ case 'pooled_sample':
2742
+ return this.formatPooledSample(data);
2743
+ case 'mz_range':
2744
+ return this.formatMzRange(data);
2745
+ case 'synthetic_peptide':
2746
+ return data.value;
2729
2747
  default:
2730
2748
  throw new Error(`Unknown syntax type: ${syntaxType}`);
2731
2749
  }
2732
2750
  }
2733
2751
  catch (error) {
2734
- console.error(`Error formatting ${syntaxType} value:`, error);
2735
2752
  return '';
2736
2753
  }
2737
2754
  }
@@ -2759,6 +2776,12 @@ class SdrfSyntaxService {
2759
2776
  return this.validateCleavageAgentDetails(data);
2760
2777
  case 'spiked_compound':
2761
2778
  return this.validateSpikedCompound(data);
2779
+ case 'pooled_sample':
2780
+ return this.validatePooledSample(data);
2781
+ case 'mz_range':
2782
+ return this.validateMzRange(data);
2783
+ case 'synthetic_peptide':
2784
+ return this.validateSyntheticPeptide(data);
2762
2785
  default:
2763
2786
  result.isValid = false;
2764
2787
  result.errors.push(`Unknown syntax type: ${syntaxType}`);
@@ -2918,16 +2941,87 @@ class SdrfSyntaxService {
2918
2941
  }
2919
2942
  return result;
2920
2943
  }
2944
+ parsePooledSample(value) {
2945
+ const trimmed = value.trim();
2946
+ if (trimmed === 'not pooled' || trimmed === 'pooled') {
2947
+ return { value: trimmed };
2948
+ }
2949
+ if (trimmed.startsWith('SN=')) {
2950
+ const rest = trimmed.slice(3);
2951
+ const sourceNames = rest.includes(';SN=')
2952
+ ? rest.split(';SN=').map(n => n.trim())
2953
+ : rest.split(',').map(n => n.trim());
2954
+ return { value: trimmed, sourceNames };
2955
+ }
2956
+ return { value: trimmed };
2957
+ }
2958
+ formatPooledSample(data) {
2959
+ if (data.value === 'not pooled' || data.value === 'pooled') {
2960
+ return data.value;
2961
+ }
2962
+ if (data.sourceNames && data.sourceNames.length > 0) {
2963
+ return 'SN=' + data.sourceNames.join(';SN=');
2964
+ }
2965
+ return data.value;
2966
+ }
2967
+ validatePooledSample(data) {
2968
+ const result = { isValid: true, errors: [] };
2969
+ if (!data.value) {
2970
+ result.isValid = false;
2971
+ result.errors.push('Pooled sample value is required');
2972
+ return result;
2973
+ }
2974
+ if (data.value !== 'not pooled' && data.value !== 'pooled' && !data.value.startsWith('SN=')) {
2975
+ result.isValid = false;
2976
+ result.errors.push('Value must be "not pooled", "pooled", or start with "SN="');
2977
+ }
2978
+ return result;
2979
+ }
2980
+ parseMzRange(value) {
2981
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*m\/z\s*-\s*(\d+(?:\.\d+)?)\s*m\/z$/i);
2982
+ if (!match) {
2983
+ throw new Error(`Invalid m/z range format: ${value}`);
2984
+ }
2985
+ return { low: parseFloat(match[1]), high: parseFloat(match[2]) };
2986
+ }
2987
+ formatMzRange(data) {
2988
+ return `${data.low}m/z-${data.high}m/z`;
2989
+ }
2990
+ validateMzRange(data) {
2991
+ const result = { isValid: true, errors: [] };
2992
+ if (data.low === undefined || data.high === undefined) {
2993
+ result.isValid = false;
2994
+ result.errors.push('Both low and high m/z values are required');
2995
+ return result;
2996
+ }
2997
+ if (data.low < 0 || data.high < 0) {
2998
+ result.isValid = false;
2999
+ result.errors.push('m/z values must be non-negative');
3000
+ }
3001
+ if (data.low >= data.high) {
3002
+ result.isValid = false;
3003
+ result.errors.push('Low m/z must be less than high m/z');
3004
+ }
3005
+ return result;
3006
+ }
3007
+ validateSyntheticPeptide(data) {
3008
+ const result = { isValid: true, errors: [] };
3009
+ if (!['synthetic', 'not synthetic'].includes(data.value)) {
3010
+ result.isValid = false;
3011
+ result.errors.push('Value must be "synthetic" or "not synthetic"');
3012
+ }
3013
+ return result;
3014
+ }
2921
3015
  getPooledSampleValues() {
2922
3016
  return [...this.POOLED_SAMPLE_VALUES];
2923
3017
  }
2924
3018
  getSyntheticPeptideValues() {
2925
3019
  return [...this.SYNTHETIC_PEPTIDE_VALUES];
2926
3020
  }
2927
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2928
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
3021
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3022
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
2929
3023
  }
2930
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSyntaxService, decorators: [{
3024
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSyntaxService, decorators: [{
2931
3025
  type: Injectable,
2932
3026
  args: [{
2933
3027
  providedIn: 'root'
@@ -3116,10 +3210,10 @@ class DesktopService {
3116
3210
  return this.wailsRuntime.Call.ByName('main.App.LogToFile', message);
3117
3211
  }
3118
3212
  }
3119
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DesktopService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3120
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DesktopService, providedIn: 'root' });
3213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: DesktopService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3214
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: DesktopService, providedIn: 'root' });
3121
3215
  }
3122
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DesktopService, decorators: [{
3216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: DesktopService, decorators: [{
3123
3217
  type: Injectable,
3124
3218
  args: [{
3125
3219
  providedIn: 'root'
@@ -3185,10 +3279,10 @@ class ElectronService {
3185
3279
  onWindowStateChange() {
3186
3280
  return this.desktop.onWindowStateChange();
3187
3281
  }
3188
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3189
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ElectronService, providedIn: 'root' });
3282
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3283
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ElectronService, providedIn: 'root' });
3190
3284
  }
3191
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ElectronService, decorators: [{
3285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ElectronService, decorators: [{
3192
3286
  type: Injectable,
3193
3287
  args: [{
3194
3288
  providedIn: 'root'
@@ -3203,10 +3297,10 @@ class ExcelLaunchService extends BaseApiService {
3203
3297
  const request = { code };
3204
3298
  return this.post(`${this.apiUrl}/excel-launch/claim/`, request);
3205
3299
  }
3206
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelLaunchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3207
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelLaunchService, providedIn: 'root' });
3300
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ExcelLaunchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3301
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ExcelLaunchService, providedIn: 'root' });
3208
3302
  }
3209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelLaunchService, decorators: [{
3303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ExcelLaunchService, decorators: [{
3210
3304
  type: Injectable,
3211
3305
  args: [{
3212
3306
  providedIn: 'root'
@@ -3310,10 +3404,10 @@ class SdrfAgeInput {
3310
3404
  }
3311
3405
  }
3312
3406
  }
3313
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3314
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3407
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3408
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3315
3409
  }
3316
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfAgeInput, decorators: [{
3410
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfAgeInput, decorators: [{
3317
3411
  type: Component,
3318
3412
  args: [{ selector: 'ccv-sdrf-age-input', imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, 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" }]
3319
3413
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3601,10 +3695,10 @@ class SdrfModificationInput {
3601
3695
  this.updateAvailableSpecifications(mockSuggestion);
3602
3696
  }
3603
3697
  }
3604
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3605
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3698
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3699
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3606
3700
  }
3607
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfModificationInput, decorators: [{
3701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfModificationInput, decorators: [{
3608
3702
  type: Component,
3609
3703
  args: [{ selector: 'ccv-sdrf-modification-input', imports: [CommonModule, ReactiveFormsModule, NgbTypeahead, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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" }]
3610
3704
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3696,10 +3790,10 @@ class SdrfSpikedCompoundInput {
3696
3790
  get showPeptideFields() {
3697
3791
  return this.spikedForm.get('CT')?.value === 'peptide';
3698
3792
  }
3699
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3700
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3793
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3794
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3701
3795
  }
3702
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3796
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3703
3797
  type: Component,
3704
3798
  args: [{ selector: 'ccv-sdrf-spiked-compound-input', imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, 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" }]
3705
3799
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3833,10 +3927,10 @@ class SdrfCleavageInput {
3833
3927
  onSearchTypeChange(type) {
3834
3928
  this.searchType.set(type);
3835
3929
  }
3836
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3837
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3931
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3838
3932
  }
3839
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3840
3934
  type: Component,
3841
3935
  args: [{ selector: 'ccv-sdrf-cleavage-input', imports: [CommonModule, ReactiveFormsModule, NgbTypeahead, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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" }]
3842
3936
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3919,10 +4013,10 @@ class SdrfNumberWithUnitInput {
3919
4013
  this.parseAndSetValue(exampleStr);
3920
4014
  this.updateValue();
3921
4015
  }
3922
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfNumberWithUnitInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
3923
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SdrfNumberWithUnitInput, isStandalone: true, selector: "ccv-sdrf-number-with-unit-input", inputs: { value: "value", units: "units", placeholder: "placeholder", description: "description", examples: "examples" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"number-with-unit-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"number\"\n [placeholder]=\"placeholder\"\n step=\"any\">\n @if (units.length > 0) {\n <select class=\"form-select\" formControlName=\"unit\" style=\"max-width: 80px;\">\n @for (unit of units; track unit) {\n <option [value]=\"unit\">{{ unit }}</option>\n }\n </select>\n }\n </div>\n </form>\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n\n @if (examples.length > 0) {\n <div class=\"examples mt-2\">\n <small class=\"text-muted me-1\">Examples:</small>\n @for (example of examples; track $index) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm me-1 mb-1\"\n style=\"font-size: 0.7rem; padding: 0.1rem 0.4rem;\"\n (click)=\"selectExample(example)\">\n {{ example }}\n </button>\n }\n </div>\n }\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4016
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfNumberWithUnitInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
4017
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SdrfNumberWithUnitInput, isStandalone: true, selector: "ccv-sdrf-number-with-unit-input", inputs: { value: "value", units: "units", placeholder: "placeholder", description: "description", examples: "examples" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"number-with-unit-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"number\"\n [placeholder]=\"placeholder\"\n step=\"any\">\n @if (units.length > 0) {\n <select class=\"form-select\" formControlName=\"unit\" style=\"max-width: 80px;\">\n @for (unit of units; track unit) {\n <option [value]=\"unit\">{{ unit }}</option>\n }\n </select>\n }\n </div>\n </form>\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n\n @if (examples.length > 0) {\n <div class=\"examples mt-2\">\n <small class=\"text-muted me-1\">Examples:</small>\n @for (example of examples; track $index) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm me-1 mb-1\"\n style=\"font-size: 0.7rem; padding: 0.1rem 0.4rem;\"\n (click)=\"selectExample(example)\">\n {{ example }}\n </button>\n }\n </div>\n }\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3924
4018
  }
3925
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfNumberWithUnitInput, decorators: [{
4019
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfNumberWithUnitInput, decorators: [{
3926
4020
  type: Component,
3927
4021
  args: [{ selector: 'ccv-sdrf-number-with-unit-input', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"number-with-unit-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"number\"\n [placeholder]=\"placeholder\"\n step=\"any\">\n @if (units.length > 0) {\n <select class=\"form-select\" formControlName=\"unit\" style=\"max-width: 80px;\">\n @for (unit of units; track unit) {\n <option [value]=\"unit\">{{ unit }}</option>\n }\n </select>\n }\n </div>\n </form>\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n\n @if (examples.length > 0) {\n <div class=\"examples mt-2\">\n <small class=\"text-muted me-1\">Examples:</small>\n @for (example of examples; track $index) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm me-1 mb-1\"\n style=\"font-size: 0.7rem; padding: 0.1rem 0.4rem;\"\n (click)=\"selectExample(example)\">\n {{ example }}\n </button>\n }\n </div>\n }\n</div>\n" }]
3928
4022
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { value: [{
@@ -3996,10 +4090,10 @@ class SdrfSelectInput {
3996
4090
  this.selectedValue.set('__custom__');
3997
4091
  }
3998
4092
  }
3999
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSelectInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4000
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SdrfSelectInput, isStandalone: true, selector: "ccv-sdrf-select-input", inputs: { value: "value", options: "options", placeholder: "placeholder", description: "description", allowCustom: "allowCustom" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"select-input\">\n @if (!isCustomMode()) {\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"selectedValue()\"\n (ngModelChange)=\"onSelectionChange($event)\">\n <option value=\"\" disabled>{{ placeholder }}</option>\n @for (option of options; track option) {\n <option [value]=\"option\">{{ option }}</option>\n }\n @if (allowCustom) {\n <option value=\"__custom__\">Custom value...</option>\n }\n </select>\n } @else {\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"customValue()\"\n (ngModelChange)=\"onCustomValueChange($event)\"\n placeholder=\"Enter custom value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"toggleCustomMode()\"\n title=\"Back to dropdown\">\n <i class=\"bi bi-list\"></i>\n </button>\n </div>\n }\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4093
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSelectInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4094
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SdrfSelectInput, isStandalone: true, selector: "ccv-sdrf-select-input", inputs: { value: "value", options: "options", placeholder: "placeholder", description: "description", allowCustom: "allowCustom" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"select-input\">\n @if (!isCustomMode()) {\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"selectedValue()\"\n (ngModelChange)=\"onSelectionChange($event)\">\n <option value=\"\" disabled>{{ placeholder }}</option>\n @for (option of options; track option) {\n <option [value]=\"option\">{{ option }}</option>\n }\n @if (allowCustom) {\n <option value=\"__custom__\">Custom value...</option>\n }\n </select>\n } @else {\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"customValue()\"\n (ngModelChange)=\"onCustomValueChange($event)\"\n placeholder=\"Enter custom value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"toggleCustomMode()\"\n title=\"Back to dropdown\">\n <i class=\"bi bi-list\"></i>\n </button>\n </div>\n }\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4001
4095
  }
4002
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSelectInput, decorators: [{
4096
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfSelectInput, decorators: [{
4003
4097
  type: Component,
4004
4098
  args: [{ selector: 'ccv-sdrf-select-input', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"select-input\">\n @if (!isCustomMode()) {\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"selectedValue()\"\n (ngModelChange)=\"onSelectionChange($event)\">\n <option value=\"\" disabled>{{ placeholder }}</option>\n @for (option of options; track option) {\n <option [value]=\"option\">{{ option }}</option>\n }\n @if (allowCustom) {\n <option value=\"__custom__\">Custom value...</option>\n }\n </select>\n } @else {\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"customValue()\"\n (ngModelChange)=\"onCustomValueChange($event)\"\n placeholder=\"Enter custom value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"toggleCustomMode()\"\n title=\"Back to dropdown\">\n <i class=\"bi bi-list\"></i>\n </button>\n </div>\n }\n\n @if (description) {\n <small class=\"text-muted d-block mt-1\">{{ description }}</small>\n }\n</div>\n" }]
4005
4099
  }], propDecorators: { value: [{
@@ -4016,6 +4110,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4016
4110
  type: Output
4017
4111
  }] } });
4018
4112
 
4113
+ class SdrfMzRangeInput {
4114
+ fb;
4115
+ value = '';
4116
+ valueChange = new EventEmitter();
4117
+ form;
4118
+ constructor(fb) {
4119
+ this.fb = fb;
4120
+ this.form = this.fb.group({
4121
+ low: [''],
4122
+ high: [''],
4123
+ });
4124
+ }
4125
+ ngOnInit() {
4126
+ if (this.value) {
4127
+ this.parseAndSet(this.value);
4128
+ }
4129
+ this.form.valueChanges.subscribe(() => this.emitValue());
4130
+ }
4131
+ ngOnChanges(changes) {
4132
+ if (changes['value'] && !changes['value'].firstChange && this.form) {
4133
+ this.parseAndSet(this.value);
4134
+ }
4135
+ }
4136
+ parseAndSet(value) {
4137
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*m\/z\s*-\s*(\d+(?:\.\d+)?)\s*m\/z$/i);
4138
+ if (match) {
4139
+ this.form.patchValue({ low: match[1], high: match[2] }, { emitEvent: false });
4140
+ }
4141
+ }
4142
+ emitValue() {
4143
+ const { low, high } = this.form.value;
4144
+ if (low && high) {
4145
+ this.valueChange.emit(`${low}m/z-${high}m/z`);
4146
+ }
4147
+ else {
4148
+ this.valueChange.emit('');
4149
+ }
4150
+ }
4151
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfMzRangeInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
4152
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SdrfMzRangeInput, isStandalone: true, selector: "ccv-sdrf-mz-range-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"mz-range-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"low\"\n placeholder=\"Low\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n <span class=\"input-group-text\">-</span>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"high\"\n placeholder=\"High\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n </div>\n </form>\n <small class=\"text-muted d-block mt-1\">e.g. 400m/z-1250m/z</small>\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4153
+ }
4154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfMzRangeInput, decorators: [{
4155
+ type: Component,
4156
+ args: [{ selector: 'ccv-sdrf-mz-range-input', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"mz-range-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"low\"\n placeholder=\"Low\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n <span class=\"input-group-text\">-</span>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"high\"\n placeholder=\"High\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n </div>\n </form>\n <small class=\"text-muted d-block mt-1\">e.g. 400m/z-1250m/z</small>\n</div>\n" }]
4157
+ }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { value: [{
4158
+ type: Input
4159
+ }], valueChange: [{
4160
+ type: Output
4161
+ }] } });
4162
+
4019
4163
  class MetadataValueEditModal {
4020
4164
  fb;
4021
4165
  activeModal;
@@ -4088,10 +4232,12 @@ class MetadataValueEditModal {
4088
4232
  ngOnInit() {
4089
4233
  this.initializeValidatorInputType();
4090
4234
  const syntaxType = this.sdrfSyntaxService.detectSpecialSyntax(this.config.columnName, this.config.columnType);
4091
- if (syntaxType && !this.validatorInputType()) {
4235
+ if (syntaxType) {
4092
4236
  this.specialSyntaxType.set(syntaxType);
4093
- this.showSpecialInput.set(true);
4094
- this.specialInputValue.set(this.config?.currentValue || '');
4237
+ if (syntaxType !== 'pooled_sample' && syntaxType !== 'synthetic_peptide' && !this.validatorInputType()) {
4238
+ this.showSpecialInput.set(true);
4239
+ this.specialInputValue.set(this.config?.currentValue || '');
4240
+ }
4095
4241
  }
4096
4242
  if (this.config?.currentValue) {
4097
4243
  this.editForm.patchValue({
@@ -4105,14 +4251,6 @@ class MetadataValueEditModal {
4105
4251
  this.loadFavoriteOptions();
4106
4252
  if (this.config?.enableMultiSampleEdit && this.config?.sampleData) {
4107
4253
  this.showSamplePanel.set(true);
4108
- console.log('Multi-sample editing enabled with', this.config.sampleData.length, 'samples');
4109
- }
4110
- else {
4111
- console.log('Multi-sample editing not enabled:', {
4112
- enableMultiSampleEdit: this.config?.enableMultiSampleEdit,
4113
- hasSampleData: !!this.config?.sampleData,
4114
- sampleDataLength: this.config?.sampleData?.length
4115
- });
4116
4254
  }
4117
4255
  this.editForm.get('value')?.valueChanges.subscribe((newValue) => {
4118
4256
  const selected = this.selectedFavorite();
@@ -4199,7 +4337,6 @@ class MetadataValueEditModal {
4199
4337
  limit: 10,
4200
4338
  searchType: this.searchType()
4201
4339
  }).pipe(map$1(response => response.suggestions || []), catchError(error => {
4202
- console.error('Error getting ontology suggestions:', error);
4203
4340
  return of([]);
4204
4341
  }), tap(() => this.isLoadingSuggestions.set(false)));
4205
4342
  }
@@ -4212,7 +4349,6 @@ class MetadataValueEditModal {
4212
4349
  customFilters: this.config.customOntologyFilters,
4213
4350
  limit: 10
4214
4351
  }).pipe(map$1(response => response.suggestions || []), catchError(error => {
4215
- console.error('Error getting ontology suggestions:', error);
4216
4352
  return of([]);
4217
4353
  }), tap(() => this.isLoadingSuggestions.set(false)));
4218
4354
  }
@@ -4352,9 +4488,6 @@ class MetadataValueEditModal {
4352
4488
  if (favorite.columnTemplate && favorite.columnTemplate !== this.config.templateId) {
4353
4489
  this.config.templateId = favorite.columnTemplate;
4354
4490
  }
4355
- if (this.hasCustomDisplayValue(favorite)) {
4356
- console.log(`Selected "${favorite.displayValue}" (value: ${favorite.value})`);
4357
- }
4358
4491
  }
4359
4492
  onFavoriteSelected(event, favoritesList) {
4360
4493
  const selectElement = event.target;
@@ -4409,9 +4542,7 @@ class MetadataValueEditModal {
4409
4542
  this.globalFavorites.set([...this.globalFavorites(), created]);
4410
4543
  }
4411
4544
  },
4412
- error: (error) => {
4413
- console.error('Error adding to favorites:', error);
4414
- }
4545
+ error: () => { }
4415
4546
  });
4416
4547
  }
4417
4548
  get hasFavorites() {
@@ -4453,8 +4584,7 @@ class MetadataValueEditModal {
4453
4584
  const labGroupIds = response.results.map(group => group.id);
4454
4585
  this.userLabGroups.set(labGroupIds);
4455
4586
  },
4456
- error: (error) => {
4457
- console.error('Error loading user lab groups:', error);
4587
+ error: () => {
4458
4588
  this.userLabGroups.set([]);
4459
4589
  }
4460
4590
  });
@@ -4478,6 +4608,7 @@ class MetadataValueEditModal {
4478
4608
  case 'modification': return 'Modification Parameters Editor';
4479
4609
  case 'cleavage': return 'Cleavage Agent Editor';
4480
4610
  case 'spiked_compound': return 'Spiked Compound Editor';
4611
+ case 'mz_range': return 'Scan Range Editor';
4481
4612
  default: return 'Special Format Editor';
4482
4613
  }
4483
4614
  }
@@ -4549,10 +4680,10 @@ class MetadataValueEditModal {
4549
4680
  }
4550
4681
  }
4551
4682
  }
4552
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", 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 });
4553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i7.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: "component", type: SdrfNumberWithUnitInput, selector: "ccv-sdrf-number-with-unit-input", inputs: ["value", "units", "placeholder", "description", "examples"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSelectInput, selector: "ccv-sdrf-select-input", inputs: ["value", "options", "placeholder", "description", "allowCustom"], outputs: ["valueChange"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", 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 });
4684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 || specialSyntaxType() === 'pooled_sample' || specialSyntaxType() === 'synthetic_peptide') {\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 @if (specialSyntaxType() === 'pooled_sample') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not pooled')\">\n Not Pooled\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('pooled')\">\n Pooled\n </button>\n }\n @if (specialSyntaxType() === 'synthetic_peptide') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('synthetic')\">\n Synthetic\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not synthetic')\">\n Not Synthetic\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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 @case ('mz_range') {\n <ccv-sdrf-mz-range-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-mz-range-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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i7.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: "component", type: SdrfNumberWithUnitInput, selector: "ccv-sdrf-number-with-unit-input", inputs: ["value", "units", "placeholder", "description", "examples"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSelectInput, selector: "ccv-sdrf-select-input", inputs: ["value", "options", "placeholder", "description", "allowCustom"], outputs: ["valueChange"] }, { kind: "component", type: SdrfMzRangeInput, selector: "ccv-sdrf-mz-range-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4554
4685
  }
4555
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataValueEditModal, decorators: [{
4686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataValueEditModal, decorators: [{
4556
4687
  type: Component,
4557
4688
  args: [{ selector: 'ccv-metadata-value-edit-modal', standalone: true, imports: [
4558
4689
  CommonModule,
@@ -4564,8 +4695,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4564
4695
  SdrfCleavageInput,
4565
4696
  SdrfSpikedCompoundInput,
4566
4697
  SdrfNumberWithUnitInput,
4567
- SdrfSelectInput
4568
- ], changeDetection: ChangeDetectionStrategy.OnPush, 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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]=\"editFormInvalid() || 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"] }]
4698
+ SdrfSelectInput,
4699
+ SdrfMzRangeInput
4700
+ ], changeDetection: ChangeDetectionStrategy.OnPush, 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 || specialSyntaxType() === 'pooled_sample' || specialSyntaxType() === 'synthetic_peptide') {\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 @if (specialSyntaxType() === 'pooled_sample') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not pooled')\">\n Not Pooled\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('pooled')\">\n Pooled\n </button>\n }\n @if (specialSyntaxType() === 'synthetic_peptide') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('synthetic')\">\n Synthetic\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not synthetic')\">\n Not Synthetic\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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 @case ('mz_range') {\n <ccv-sdrf-mz-range-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-mz-range-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]=\"editFormInvalid() || 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"] }]
4569
4701
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i3.AuthService }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: OntologySearchService }], propDecorators: { config: [{
4570
4702
  type: Input
4571
4703
  }], valueSaved: [{
@@ -4651,10 +4783,10 @@ class ExcelExportModalComponent {
4651
4783
  cancel() {
4652
4784
  this.activeModal.dismiss();
4653
4785
  }
4654
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4655
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4786
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4787
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4656
4788
  }
4657
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
4789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
4658
4790
  type: Component,
4659
4791
  args: [{ selector: 'ccv-excel-export-modal', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, 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" }]
4660
4792
  }] });
@@ -4671,10 +4803,10 @@ class ValidationResultsModal {
4671
4803
  return timestamp;
4672
4804
  }
4673
4805
  }
4674
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4675
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ValidationResultsModal, isStandalone: true, selector: "ccv-validation-results-modal", inputs: { results: "results" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export or validation</li>\n <li>Check the <a href=\"https://sdrf.quantms.org\" target=\"_blank\">SDRF Specification</a> for detailed explanations of column and their requirements</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4806
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4807
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: ValidationResultsModal, isStandalone: true, selector: "ccv-validation-results-modal", inputs: { results: "results" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export or validation</li>\n <li>Check the <a href=\"https://sdrf.quantms.org\" target=\"_blank\">SDRF Specification</a> for detailed explanations of column and their requirements</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4676
4808
  }
4677
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ValidationResultsModal, decorators: [{
4809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ValidationResultsModal, decorators: [{
4678
4810
  type: Component,
4679
4811
  args: [{ selector: 'ccv-validation-results-modal', standalone: true, imports: [CommonModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export or validation</li>\n <li>Check the <a href=\"https://sdrf.quantms.org\" target=\"_blank\">SDRF Specification</a> for detailed explanations of column and their requirements</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"] }]
4680
4812
  }], propDecorators: { results: [{
@@ -5083,10 +5215,10 @@ class AsyncTaskUIService {
5083
5215
  break;
5084
5216
  }
5085
5217
  }
5086
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5087
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
5218
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5219
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
5088
5220
  }
5089
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskUIService, decorators: [{
5221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AsyncTaskUIService, decorators: [{
5090
5222
  type: Injectable,
5091
5223
  args: [{
5092
5224
  providedIn: 'root'
@@ -5195,10 +5327,10 @@ class MetadataValidationModal {
5195
5327
  }
5196
5328
  });
5197
5329
  }
5198
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
5199
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MetadataValidationModal, isStandalone: true, selector: "ccv-metadata-validation-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\n Validate Metadata Table\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n </button>\n</div>\n\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"modal-body\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards.\n </div>\n\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"bi bi-diagram-3 me-1\"></i>\n Validation Schemas\n <span class=\"badge bg-primary ms-2\">{{ selectedSchemas().size }} selected</span>\n </h6>\n\n <div class=\"d-flex gap-2 mb-2\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Clear All\n </button>\n </div>\n\n @if (isLoadingSchemas()) {\n <div class=\"text-center py-3\">\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n Loading schemas...\n </div>\n } @else {\n <div class=\"schema-list border rounded p-2\" style=\"max-height: 200px; overflow-y: auto;\">\n @for (schema of availableSchemas(); track schema.name) {\n <div\n class=\"schema-item d-flex align-items-center p-2 rounded cursor-pointer\"\n [class.bg-primary-subtle]=\"isSchemaSelected(schema.name)\"\n [class.border-primary]=\"isSchemaSelected(schema.name)\"\n (click)=\"toggleSchema(schema.name)\">\n <input\n type=\"checkbox\"\n class=\"form-check-input me-2\"\n [checked]=\"isSchemaSelected(schema.name)\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-medium\">{{ schema.displayName }}</div>\n @if (schema.description) {\n <small class=\"text-muted\">{{ schema.description }}</small>\n }\n </div>\n @if (schema.columnCount > 0) {\n <span class=\"badge bg-secondary ms-2\">{{ schema.columnCount }} cols</span>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">Validation Options</h6>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"validate_sdrf_format\"\n formControlName=\"validate_sdrf_format\">\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\n SDRF Format Validation\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"include_pools\"\n formControlName=\"include_pools\">\n <label class=\"form-check-label\" for=\"include_pools\">\n Include Sample Pools\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"skip_ontology\"\n formControlName=\"skip_ontology\">\n <label class=\"form-check-label\" for=\"skip_ontology\">\n Skip Ontology Validation\n </label>\n <small class=\"form-text text-muted d-block\">Faster validation without OLS lookups</small>\n </div>\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">What gets checked?</h6>\n <div class=\"validation-info small\">\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Required columns presence\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n SDRF format compliance\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Column header validation\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Data structure integrity\n </div>\n </div>\n </div>\n </div>\n\n @if (validationError()) {\n <div class=\"alert alert-danger mt-3 py-2\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ validationError() }}\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary btn-sm\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n Cancel\n </button>\n\n <button\n type=\"submit\"\n class=\"btn btn-success btn-sm\"\n [disabled]=\"validationFormInvalid() || isValidating() || selectedSchemas().size === 0\">\n @if (isValidating()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\n Starting...\n } @else {\n <i class=\"bi bi-play me-1\"></i>\n Validate ({{ selectedSchemas().size }} schema{{ selectedSchemas().size !== 1 ? 's' : '' }})\n }\n </button>\n </div>\n</form>\n", styles: [".validation-info{border-radius:.375rem;padding:.75rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}.schema-list{background-color:var(--bs-body-bg)}.schema-item{cursor:pointer;transition:background-color .15s ease;border:1px solid transparent;margin-bottom:.25rem}.schema-item:last-child{margin-bottom:0}.schema-item:hover{background-color:var(--bs-tertiary-bg)}.schema-item.bg-primary-subtle{border-color:var(--bs-primary)}.cursor-pointer{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5330
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
5331
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: MetadataValidationModal, isStandalone: true, selector: "ccv-metadata-validation-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\n Validate Metadata Table\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n </button>\n</div>\n\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"modal-body\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards.\n </div>\n\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"bi bi-diagram-3 me-1\"></i>\n Validation Schemas\n <span class=\"badge bg-primary ms-2\">{{ selectedSchemas().size }} selected</span>\n </h6>\n\n <div class=\"d-flex gap-2 mb-2\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Clear All\n </button>\n </div>\n\n @if (isLoadingSchemas()) {\n <div class=\"text-center py-3\">\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n Loading schemas...\n </div>\n } @else {\n <div class=\"schema-list border rounded p-2\" style=\"max-height: 200px; overflow-y: auto;\">\n @for (schema of availableSchemas(); track schema.name) {\n <div\n class=\"schema-item d-flex align-items-center p-2 rounded cursor-pointer\"\n [class.bg-primary-subtle]=\"isSchemaSelected(schema.name)\"\n [class.border-primary]=\"isSchemaSelected(schema.name)\"\n (click)=\"toggleSchema(schema.name)\">\n <input\n type=\"checkbox\"\n class=\"form-check-input me-2\"\n [checked]=\"isSchemaSelected(schema.name)\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-medium\">{{ schema.displayName }}</div>\n @if (schema.description) {\n <small class=\"text-muted\">{{ schema.description }}</small>\n }\n </div>\n @if (schema.columnCount > 0) {\n <span class=\"badge bg-secondary ms-2\">{{ schema.columnCount }} cols</span>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">Validation Options</h6>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"validate_sdrf_format\"\n formControlName=\"validate_sdrf_format\">\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\n SDRF Format Validation\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"include_pools\"\n formControlName=\"include_pools\">\n <label class=\"form-check-label\" for=\"include_pools\">\n Include Sample Pools\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"skip_ontology\"\n formControlName=\"skip_ontology\">\n <label class=\"form-check-label\" for=\"skip_ontology\">\n Skip Ontology Validation\n </label>\n <small class=\"form-text text-muted d-block\">Faster validation without OLS lookups</small>\n </div>\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">What gets checked?</h6>\n <div class=\"validation-info small\">\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Required columns presence\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n SDRF format compliance\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Column header validation\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Data structure integrity\n </div>\n </div>\n </div>\n </div>\n\n @if (validationError()) {\n <div class=\"alert alert-danger mt-3 py-2\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ validationError() }}\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary btn-sm\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n Cancel\n </button>\n\n <button\n type=\"submit\"\n class=\"btn btn-success btn-sm\"\n [disabled]=\"validationFormInvalid() || isValidating() || selectedSchemas().size === 0\">\n @if (isValidating()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\n Starting...\n } @else {\n <i class=\"bi bi-play me-1\"></i>\n Validate ({{ selectedSchemas().size }} schema{{ selectedSchemas().size !== 1 ? 's' : '' }})\n }\n </button>\n </div>\n</form>\n", styles: [".validation-info{border-radius:.375rem;padding:.75rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}.schema-list{background-color:var(--bs-body-bg)}.schema-item{cursor:pointer;transition:background-color .15s ease;border:1px solid transparent;margin-bottom:.25rem}.schema-item:last-child{margin-bottom:0}.schema-item:hover{background-color:var(--bs-tertiary-bg)}.schema-item.bg-primary-subtle{border-color:var(--bs-primary)}.cursor-pointer{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5200
5332
  }
5201
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataValidationModal, decorators: [{
5333
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataValidationModal, decorators: [{
5202
5334
  type: Component,
5203
5335
  args: [{ selector: 'ccv-metadata-validation-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\n Validate Metadata Table\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n </button>\n</div>\n\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"modal-body\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards.\n </div>\n\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"bi bi-diagram-3 me-1\"></i>\n Validation Schemas\n <span class=\"badge bg-primary ms-2\">{{ selectedSchemas().size }} selected</span>\n </h6>\n\n <div class=\"d-flex gap-2 mb-2\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearAllSchemas()\"\n [disabled]=\"isLoadingSchemas()\">\n Clear All\n </button>\n </div>\n\n @if (isLoadingSchemas()) {\n <div class=\"text-center py-3\">\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n Loading schemas...\n </div>\n } @else {\n <div class=\"schema-list border rounded p-2\" style=\"max-height: 200px; overflow-y: auto;\">\n @for (schema of availableSchemas(); track schema.name) {\n <div\n class=\"schema-item d-flex align-items-center p-2 rounded cursor-pointer\"\n [class.bg-primary-subtle]=\"isSchemaSelected(schema.name)\"\n [class.border-primary]=\"isSchemaSelected(schema.name)\"\n (click)=\"toggleSchema(schema.name)\">\n <input\n type=\"checkbox\"\n class=\"form-check-input me-2\"\n [checked]=\"isSchemaSelected(schema.name)\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-medium\">{{ schema.displayName }}</div>\n @if (schema.description) {\n <small class=\"text-muted\">{{ schema.description }}</small>\n }\n </div>\n @if (schema.columnCount > 0) {\n <span class=\"badge bg-secondary ms-2\">{{ schema.columnCount }} cols</span>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">Validation Options</h6>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"validate_sdrf_format\"\n formControlName=\"validate_sdrf_format\">\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\n SDRF Format Validation\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"include_pools\"\n formControlName=\"include_pools\">\n <label class=\"form-check-label\" for=\"include_pools\">\n Include Sample Pools\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"skip_ontology\"\n formControlName=\"skip_ontology\">\n <label class=\"form-check-label\" for=\"skip_ontology\">\n Skip Ontology Validation\n </label>\n <small class=\"form-text text-muted d-block\">Faster validation without OLS lookups</small>\n </div>\n </div>\n\n <div class=\"col-md-6\">\n <h6 class=\"mb-2\">What gets checked?</h6>\n <div class=\"validation-info small\">\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Required columns presence\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n SDRF format compliance\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Column header validation\n </div>\n <div class=\"mb-1\">\n <i class=\"bi bi-check-circle text-success me-1\"></i>\n Data structure integrity\n </div>\n </div>\n </div>\n </div>\n\n @if (validationError()) {\n <div class=\"alert alert-danger mt-3 py-2\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ validationError() }}\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary btn-sm\"\n (click)=\"activeModal.dismiss()\"\n [disabled]=\"isValidating()\">\n Cancel\n </button>\n\n <button\n type=\"submit\"\n class=\"btn btn-success btn-sm\"\n [disabled]=\"validationFormInvalid() || isValidating() || selectedSchemas().size === 0\">\n @if (isValidating()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\n Starting...\n } @else {\n <i class=\"bi bi-play me-1\"></i>\n Validate ({{ selectedSchemas().size }} schema{{ selectedSchemas().size !== 1 ? 's' : '' }})\n }\n </button>\n </div>\n</form>\n", styles: [".validation-info{border-radius:.375rem;padding:.75rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}.schema-list{background-color:var(--bs-body-bg)}.schema-item{cursor:pointer;transition:background-color .15s ease;border:1px solid transparent;margin-bottom:.25rem}.schema-item:last-child{margin-bottom:0}.schema-item:hover{background-color:var(--bs-tertiary-bg)}.schema-item.bg-primary-subtle{border-color:var(--bs-primary)}.cursor-pointer{cursor:pointer}\n"] }]
5204
5336
  }], propDecorators: { config: [{
@@ -5416,10 +5548,10 @@ class MetadataTableEditModal {
5416
5548
  };
5417
5549
  return displayNames[fieldName] || fieldName;
5418
5550
  }
5419
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableService }, { token: i3.LabGroupService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
5420
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5551
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableService }, { token: i3.LabGroupService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
5552
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5421
5553
  }
5422
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableEditModal, decorators: [{
5554
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableEditModal, decorators: [{
5423
5555
  type: Component,
5424
5556
  args: [{ selector: 'ccv-metadata-table-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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]=\"editFormInvalid() || 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" }]
5425
5557
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableService }, { type: i3.LabGroupService }, { type: i3.ConfirmDialogService }], propDecorators: { table: [{
@@ -5890,10 +6022,10 @@ class ColumnEditModal {
5890
6022
  customFilters
5891
6023
  };
5892
6024
  }
5893
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataColumnTemplateService }, { token: SchemaService }, { token: SdrfSyntaxService }, { token: OfficialColumnCacheService }], target: i0.ɵɵFactoryTarget.Component });
5894
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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 existing templates to ensure data compliance and consistency.\n </p>\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 (min 3 characters)...\"\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 if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0) {\n <option value=\"\" disabled>No templates found</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) }})}@if (template.isSystemTemplate) { [Official]}\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 <!-- Validation Error -->\n @if (validationError()) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ validationError() }}\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 (Select from official SDRF columns or custom templates)\n } @else {\n (Optional - select a template to change column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <!-- Filter Options -->\n <div class=\"row mb-2 g-2\">\n <div class=\"col-auto\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"allTemplates\"\n [checked]=\"!showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(false)\">\n <label class=\"btn btn-outline-primary\" for=\"allTemplates\">All Templates</label>\n\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"systemTemplates\"\n [checked]=\"showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(true)\">\n <label class=\"btn btn-outline-primary\" for=\"systemTemplates\">\n <i class=\"bi bi-shield-check me-1\"></i>Official Only\n </label>\n </div>\n </div>\n @if (availableSchemas.length > 0) {\n <div class=\"col-auto\">\n <select\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"selectedSchemaFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onSchemaFilterChange(selectedSchemaFilter)\">\n <option value=\"\">All Schemas</option>\n @for (schema of availableSchemas; track schema.id) {\n <option [value]=\"schema.name\">{{ schema.displayName || schema.name }}</option>\n }\n </select>\n </div>\n }\n </div>\n\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 (min 3 characters)...\"\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=\"\">-- Select a template --</option>\n @if (isLoadingTemplates()) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0 && templateFilter.length === 0) {\n <option value=\"\" disabled>Search above to find column templates...</option>\n } @else if (templates.length === 0 && templateFilter.length >= 3) {\n <option value=\"\" disabled>No templates found matching \"{{ templateFilter }}\"</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n @if (template.isSystemTemplate) {<i class=\"bi bi-shield-check\"></i> }{{ 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.columnName }}\" selected.\n @if (selectedTemplate.isSystemTemplate) {\n <span class=\"badge bg-success ms-1\">Official SDRF</span>\n }\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 a template above\">\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 a template above to set the column name and configuration.\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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6025
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataColumnTemplateService }, { token: SchemaService }, { token: SdrfSyntaxService }, { token: OfficialColumnCacheService }], target: i0.ɵɵFactoryTarget.Component });
6026
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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 existing templates to ensure data compliance and consistency.\n </p>\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 (min 3 characters)...\"\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 if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0) {\n <option value=\"\" disabled>No templates found</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) }})}@if (template.isSystemTemplate) { [Official]}\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 <!-- Validation Error -->\n @if (validationError()) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ validationError() }}\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 (Select from official SDRF columns or custom templates)\n } @else {\n (Optional - select a template to change column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <!-- Filter Options -->\n <div class=\"row mb-2 g-2\">\n <div class=\"col-auto\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"allTemplates\"\n [checked]=\"!showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(false)\">\n <label class=\"btn btn-outline-primary\" for=\"allTemplates\">All Templates</label>\n\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"systemTemplates\"\n [checked]=\"showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(true)\">\n <label class=\"btn btn-outline-primary\" for=\"systemTemplates\">\n <i class=\"bi bi-shield-check me-1\"></i>Official Only\n </label>\n </div>\n </div>\n @if (availableSchemas.length > 0) {\n <div class=\"col-auto\">\n <select\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"selectedSchemaFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onSchemaFilterChange(selectedSchemaFilter)\">\n <option value=\"\">All Schemas</option>\n @for (schema of availableSchemas; track schema.id) {\n <option [value]=\"schema.name\">{{ schema.displayName || schema.name }}</option>\n }\n </select>\n </div>\n }\n </div>\n\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 (min 3 characters)...\"\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=\"\">-- Select a template --</option>\n @if (isLoadingTemplates()) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0 && templateFilter.length === 0) {\n <option value=\"\" disabled>Search above to find column templates...</option>\n } @else if (templates.length === 0 && templateFilter.length >= 3) {\n <option value=\"\" disabled>No templates found matching \"{{ templateFilter }}\"</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n @if (template.isSystemTemplate) {<i class=\"bi bi-shield-check\"></i> }{{ 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.columnName }}\" selected.\n @if (selectedTemplate.isSystemTemplate) {\n <span class=\"badge bg-success ms-1\">Official SDRF</span>\n }\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 a template above\">\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 a template above to set the column name and configuration.\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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5895
6027
  }
5896
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnEditModal, decorators: [{
6028
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnEditModal, decorators: [{
5897
6029
  type: Component,
5898
6030
  args: [{ selector: 'ccv-column-edit-modal', imports: [CommonModule, ReactiveFormsModule, FormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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 existing templates to ensure data compliance and consistency.\n </p>\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 (min 3 characters)...\"\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 if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0) {\n <option value=\"\" disabled>No templates found</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) }})}@if (template.isSystemTemplate) { [Official]}\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 <!-- Validation Error -->\n @if (validationError()) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ validationError() }}\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 (Select from official SDRF columns or custom templates)\n } @else {\n (Optional - select a template to change column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <!-- Filter Options -->\n <div class=\"row mb-2 g-2\">\n <div class=\"col-auto\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"allTemplates\"\n [checked]=\"!showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(false)\">\n <label class=\"btn btn-outline-primary\" for=\"allTemplates\">All Templates</label>\n\n <input type=\"radio\" class=\"btn-check\" name=\"templateType\" id=\"systemTemplates\"\n [checked]=\"showSystemTemplatesOnly\" (change)=\"toggleSystemTemplatesFilter(true)\">\n <label class=\"btn btn-outline-primary\" for=\"systemTemplates\">\n <i class=\"bi bi-shield-check me-1\"></i>Official Only\n </label>\n </div>\n </div>\n @if (availableSchemas.length > 0) {\n <div class=\"col-auto\">\n <select\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"selectedSchemaFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onSchemaFilterChange(selectedSchemaFilter)\">\n <option value=\"\">All Schemas</option>\n @for (schema of availableSchemas; track schema.id) {\n <option [value]=\"schema.name\">{{ schema.displayName || schema.name }}</option>\n }\n </select>\n </div>\n }\n </div>\n\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 (min 3 characters)...\"\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=\"\">-- Select a template --</option>\n @if (isLoadingTemplates()) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else if (templateFilter.length > 0 && templateFilter.length < 3) {\n <option value=\"\" disabled>Type at least 3 characters to search...</option>\n } @else if (templates.length === 0 && templateFilter.length === 0) {\n <option value=\"\" disabled>Search above to find column templates...</option>\n } @else if (templates.length === 0 && templateFilter.length >= 3) {\n <option value=\"\" disabled>No templates found matching \"{{ templateFilter }}\"</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n @if (template.isSystemTemplate) {<i class=\"bi bi-shield-check\"></i> }{{ 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.columnName }}\" selected.\n @if (selectedTemplate.isSystemTemplate) {\n <span class=\"badge bg-success ms-1\">Official SDRF</span>\n }\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 a template above\">\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 a template above to set the column name and configuration.\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]=\"editFormInvalid() || 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"] }]
5899
6031
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataColumnTemplateService }, { type: SchemaService }, { type: SdrfSyntaxService }, { type: OfficialColumnCacheService }], propDecorators: { column: [{
@@ -6332,10 +6464,10 @@ class MetadataTableTemplateEditModal {
6332
6464
  columns.length = 0;
6333
6465
  columns.push(...regroupedColumns);
6334
6466
  }
6335
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: MetadataColumnService }, { token: i3.ToastService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
6336
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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=\"labGroup\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\"\n class=\"column-row\"\n [class.recently-modified]=\"column.id && recentlyModifiedColumnId() === column.id\"\n [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editFormInvalid() || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.column-row.recently-modified{animation:highlight-row 3s ease-in-out}@keyframes highlight-row{0%{background-color:rgba(var(--bs-success-rgb),.3)}50%{background-color:rgba(var(--bs-success-rgb),.15)}to{background-color:transparent}}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6467
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: MetadataColumnService }, { token: i3.ToastService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
6468
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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=\"labGroup\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\"\n class=\"column-row\"\n [class.recently-modified]=\"column.id && recentlyModifiedColumnId() === column.id\"\n [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editFormInvalid() || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.column-row.recently-modified{animation:highlight-row 3s ease-in-out}@keyframes highlight-row{0%{background-color:rgba(var(--bs-success-rgb),.3)}50%{background-color:rgba(var(--bs-success-rgb),.15)}to{background-color:transparent}}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6337
6469
  }
6338
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
6470
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
6339
6471
  type: Component,
6340
6472
  args: [{ selector: 'ccv-metadata-table-template-edit-modal', imports: [CommonModule, ReactiveFormsModule, NgbModule, DragDropModule], changeDetection: ChangeDetectionStrategy.OnPush, 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=\"labGroup\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\"\n class=\"column-row\"\n [class.recently-modified]=\"column.id && recentlyModifiedColumnId() === column.id\"\n [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editFormInvalid() || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.column-row.recently-modified{animation:highlight-row 3s ease-in-out}@keyframes highlight-row{0%{background-color:rgba(var(--bs-success-rgb),.3)}50%{background-color:rgba(var(--bs-success-rgb),.15)}to{background-color:transparent}}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"] }]
6341
6473
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: MetadataColumnService }, { type: i3.ToastService }, { type: i3.ConfirmDialogService }], propDecorators: { template: [{
@@ -6537,10 +6669,10 @@ class TemplateSchemaSelectionModal {
6537
6669
  onCancel() {
6538
6670
  this.activeModal.dismiss();
6539
6671
  }
6540
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TemplateSchemaSelectionModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6541
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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 by Layer -->\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-layers me-1\"></i>Select Schemas by Layer\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=\"alert alert-info py-2 mb-3\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>Select a <strong>Technology</strong> schema as your base, then add <strong>Sample/Organism</strong> and <strong>Experiment</strong> schemas as needed.</small>\n </div>\n\n @for (layer of getLayerKeys(); track layer) {\n @if (hasLayerSchemas(layer)) {\n <div class=\"layer-section mb-3\">\n <div class=\"layer-header d-flex align-items-center mb-2\">\n <span class=\"badge bg-{{ layerInfo[layer].color }} me-2\">\n <i class=\"bi {{ layerInfo[layer].icon }} me-1\"></i>{{ layerInfo[layer].label }}\n </span>\n <small class=\"text-muted\">{{ layerInfo[layer].description }}</small>\n </div>\n <div class=\"row\">\n @for (schema of getLayerSchemas(layer); track schema.id) {\n <div class=\"col-md-6 col-lg-4 mb-2\">\n <div class=\"card schema-card h-100\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n [class.border-primary]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-2\">\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 flex-column\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"schema-title small\">{{ schema.displayName || schema.name }}</strong>\n @if (schema.usableAlone) {\n <span class=\"badge bg-success ms-1\" style=\"font-size: 0.6rem;\">Standalone</span>\n }\n @if (schema.version) {\n <span class=\"badge bg-light text-dark ms-1\" style=\"font-size: 0.6rem;\">v{{ schema.version }}</span>\n }\n </div>\n @if (schema.extends) {\n <small class=\"text-muted\" style=\"font-size: 0.7rem;\">\n <i class=\"bi bi-arrow-return-right me-1\"></i>extends {{ schema.extends }}\n </small>\n }\n @if (schema.description) {\n <small class=\"schema-description text-muted mt-1\" style=\"font-size: 0.7rem; line-height: 1.2;\">\n {{ schema.description | slice:0:80 }}{{ schema.description && schema.description.length > 80 ? '...' : '' }}\n </small>\n }\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n }\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3 py-2\" 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=\"labGroup\">\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-{{ layerInfo[getSchemaLayer(schemaId)]?.color || 'secondary' }} py-2 px-3\">\n <i class=\"bi {{ layerInfo[getSchemaLayer(schemaId)]?.icon || 'bi-grid' }} me-1\"></i>\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 <i class=\"bi bi-info-circle me-1\"></i>\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n Duplicate columns across schemas will be merged.\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: [".layer-section{padding:.75rem;background-color:var(--cupcake-bg-tertiary);border-radius:.5rem}.layer-section .layer-header{padding-bottom:.5rem;border-bottom:1px solid var(--cupcake-border);margin-bottom:.75rem}.schema-card{cursor:pointer;transition:all .15s ease;border:1px solid var(--cupcake-border);background-color:var(--cupcake-card-bg)}.schema-card:hover{border-color:var(--cupcake-primary);box-shadow:0 2px 6px var(--cupcake-shadow);transform:translateY(-1px)}.schema-card.selected{border-color:var(--cupcake-primary);background-color:rgba(var(--cupcake-primary-rgb),.1);box-shadow:0 0 0 2px rgba(var(--cupcake-primary-rgb),.2)}.schema-title{font-size:.85em;color:var(--cupcake-text);line-height:1.2}.schema-description{font-size:.75em;line-height:1.25;color:var(--cupcake-text-muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.schema-metadata{font-size:.7em}.schema-metadata .badge{font-size:.6em}.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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6672
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: TemplateSchemaSelectionModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6673
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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 by Layer -->\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-layers me-1\"></i>Select Schemas by Layer\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=\"alert alert-info py-2 mb-3\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>Select a <strong>Technology</strong> schema as your base, then add <strong>Sample/Organism</strong> and <strong>Experiment</strong> schemas as needed.</small>\n </div>\n\n @for (layer of getLayerKeys(); track layer) {\n @if (hasLayerSchemas(layer)) {\n <div class=\"layer-section mb-3\">\n <div class=\"layer-header d-flex align-items-center mb-2\">\n <span class=\"badge bg-{{ layerInfo[layer].color }} me-2\">\n <i class=\"bi {{ layerInfo[layer].icon }} me-1\"></i>{{ layerInfo[layer].label }}\n </span>\n <small class=\"text-muted\">{{ layerInfo[layer].description }}</small>\n </div>\n <div class=\"row\">\n @for (schema of getLayerSchemas(layer); track schema.id) {\n <div class=\"col-md-6 col-lg-4 mb-2\">\n <div class=\"card schema-card h-100\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n [class.border-primary]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-2\">\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 flex-column\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"schema-title small\">{{ schema.displayName || schema.name }}</strong>\n @if (schema.usableAlone) {\n <span class=\"badge bg-success ms-1\" style=\"font-size: 0.6rem;\">Standalone</span>\n }\n @if (schema.version) {\n <span class=\"badge bg-light text-dark ms-1\" style=\"font-size: 0.6rem;\">v{{ schema.version }}</span>\n }\n </div>\n @if (schema.extends) {\n <small class=\"text-muted\" style=\"font-size: 0.7rem;\">\n <i class=\"bi bi-arrow-return-right me-1\"></i>extends {{ schema.extends }}\n </small>\n }\n @if (schema.description) {\n <small class=\"schema-description text-muted mt-1\" style=\"font-size: 0.7rem; line-height: 1.2;\">\n {{ schema.description | slice:0:80 }}{{ schema.description && schema.description.length > 80 ? '...' : '' }}\n </small>\n }\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n }\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3 py-2\" 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=\"labGroup\">\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-{{ layerInfo[getSchemaLayer(schemaId)]?.color || 'secondary' }} py-2 px-3\">\n <i class=\"bi {{ layerInfo[getSchemaLayer(schemaId)]?.icon || 'bi-grid' }} me-1\"></i>\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 <i class=\"bi bi-info-circle me-1\"></i>\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n Duplicate columns across schemas will be merged.\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: [".layer-section{padding:.75rem;background-color:var(--cupcake-bg-tertiary);border-radius:.5rem}.layer-section .layer-header{padding-bottom:.5rem;border-bottom:1px solid var(--cupcake-border);margin-bottom:.75rem}.schema-card{cursor:pointer;transition:all .15s ease;border:1px solid var(--cupcake-border);background-color:var(--cupcake-card-bg)}.schema-card:hover{border-color:var(--cupcake-primary);box-shadow:0 2px 6px var(--cupcake-shadow);transform:translateY(-1px)}.schema-card.selected{border-color:var(--cupcake-primary);background-color:rgba(var(--cupcake-primary-rgb),.1);box-shadow:0 0 0 2px rgba(var(--cupcake-primary-rgb),.2)}.schema-title{font-size:.85em;color:var(--cupcake-text);line-height:1.2}.schema-description{font-size:.75em;line-height:1.25;color:var(--cupcake-text-muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.schema-metadata{font-size:.7em}.schema-metadata .badge{font-size:.6em}.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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6542
6674
  }
6543
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
6675
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
6544
6676
  type: Component,
6545
6677
  args: [{ selector: 'ccv-template-schema-selection-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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 by Layer -->\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-layers me-1\"></i>Select Schemas by Layer\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=\"alert alert-info py-2 mb-3\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>Select a <strong>Technology</strong> schema as your base, then add <strong>Sample/Organism</strong> and <strong>Experiment</strong> schemas as needed.</small>\n </div>\n\n @for (layer of getLayerKeys(); track layer) {\n @if (hasLayerSchemas(layer)) {\n <div class=\"layer-section mb-3\">\n <div class=\"layer-header d-flex align-items-center mb-2\">\n <span class=\"badge bg-{{ layerInfo[layer].color }} me-2\">\n <i class=\"bi {{ layerInfo[layer].icon }} me-1\"></i>{{ layerInfo[layer].label }}\n </span>\n <small class=\"text-muted\">{{ layerInfo[layer].description }}</small>\n </div>\n <div class=\"row\">\n @for (schema of getLayerSchemas(layer); track schema.id) {\n <div class=\"col-md-6 col-lg-4 mb-2\">\n <div class=\"card schema-card h-100\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n [class.border-primary]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-2\">\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 flex-column\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"schema-title small\">{{ schema.displayName || schema.name }}</strong>\n @if (schema.usableAlone) {\n <span class=\"badge bg-success ms-1\" style=\"font-size: 0.6rem;\">Standalone</span>\n }\n @if (schema.version) {\n <span class=\"badge bg-light text-dark ms-1\" style=\"font-size: 0.6rem;\">v{{ schema.version }}</span>\n }\n </div>\n @if (schema.extends) {\n <small class=\"text-muted\" style=\"font-size: 0.7rem;\">\n <i class=\"bi bi-arrow-return-right me-1\"></i>extends {{ schema.extends }}\n </small>\n }\n @if (schema.description) {\n <small class=\"schema-description text-muted mt-1\" style=\"font-size: 0.7rem; line-height: 1.2;\">\n {{ schema.description | slice:0:80 }}{{ schema.description && schema.description.length > 80 ? '...' : '' }}\n </small>\n }\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n }\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3 py-2\" 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=\"labGroup\">\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-{{ layerInfo[getSchemaLayer(schemaId)]?.color || 'secondary' }} py-2 px-3\">\n <i class=\"bi {{ layerInfo[getSchemaLayer(schemaId)]?.icon || 'bi-grid' }} me-1\"></i>\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 <i class=\"bi bi-info-circle me-1\"></i>\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n Duplicate columns across schemas will be merged.\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: [".layer-section{padding:.75rem;background-color:var(--cupcake-bg-tertiary);border-radius:.5rem}.layer-section .layer-header{padding-bottom:.5rem;border-bottom:1px solid var(--cupcake-border);margin-bottom:.75rem}.schema-card{cursor:pointer;transition:all .15s ease;border:1px solid var(--cupcake-border);background-color:var(--cupcake-card-bg)}.schema-card:hover{border-color:var(--cupcake-primary);box-shadow:0 2px 6px var(--cupcake-shadow);transform:translateY(-1px)}.schema-card.selected{border-color:var(--cupcake-primary);background-color:rgba(var(--cupcake-primary-rgb),.1);box-shadow:0 0 0 2px rgba(var(--cupcake-primary-rgb),.2)}.schema-title{font-size:.85em;color:var(--cupcake-text);line-height:1.2}.schema-description{font-size:.75em;line-height:1.25;color:var(--cupcake-text-muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.schema-metadata{font-size:.7em}.schema-metadata .badge{font-size:.6em}.form-check-input{pointer-events:none}.modal-body{max-height:80vh;overflow-y:auto}.badge.fs-6{font-size:.875rem!important}\n"] }]
6546
6678
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { availableSchemas: [{
@@ -6650,10 +6782,10 @@ class SchemaSelectionModal {
6650
6782
  };
6651
6783
  return categoryMap[category.toLowerCase()] || 'bg-secondary';
6652
6784
  }
6653
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SchemaSelectionModal, deps: [{ token: i2.NgbActiveModal }, { token: SchemaService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
6654
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SchemaSelectionModal, isStandalone: true, selector: "ccv-schema-selection-modal", ngImport: i0, template: "<!-- Schema Selection Modal -->\n<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Description -->\n <div class=\"alert alert-info mb-4\">\n <div class=\"d-flex align-items-start\">\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\n <div>\n <small>{{ description }}</small>\n <br>\n <small class=\"text-muted\">\n Leave empty to use all available schemas or select specific ones for custom ordering.\n </small>\n </div>\n </div>\n </div>\n\n <!-- Search and Actions -->\n <div class=\"row mb-3\">\n <div class=\"col-md-8\">\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 placeholder=\"Search schemas...\"\n [(ngModel)]=\"searchTerm\"\n [ngModelOptions]=\"{updateOn: 'change'}\"\n >\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"btn-group w-100\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"selectAll()\"\n [disabled]=\"filteredSchemas.length === 0\"\n title=\"Select All Visible\">\n <i class=\"bi bi-check-all\"></i> All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearSelection()\"\n [disabled]=\"selectedSchemaIds().size === 0\"\n title=\"Clear Selection\">\n <i class=\"bi bi-x-circle\"></i> Clear\n </button>\n </div>\n </div>\n </div>\n\n <!-- Selection Summary -->\n @if (selectedSchemaIds().size > 0) {\n <div class=\"alert alert-success mb-3\">\n <i class=\"bi bi-check-circle me-2\"></i>\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\n </div>\n }\n\n <!-- Loading State -->\n @if (isLoading()) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading schemas...</span>\n </div>\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\n </div>\n } @else {\n <!-- Schema List -->\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @if (filteredSchemas.length === 0) {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\n <h6>No schemas found</h6>\n <p class=\"small mb-0\">\n @if (searchTerm()) {\n Try adjusting your search terms\n } @else {\n No schemas are currently available\n }\n </p>\n </div>\n } @else {\n @for (schema of filteredSchemas; track schema.id) {\n <div\n class=\"schema-item border rounded p-3 mb-2\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchemaSelection(schema.id)\"\n role=\"button\">\n\n <div class=\"d-flex align-items-start\">\n <!-- Checkbox -->\n <div class=\"form-check me-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [checked]=\"isSchemaSelected(schema.id)\"\n [id]=\"'schema-' + schema.id\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\n Select {{ schema.name }}\n </label>\n </div>\n\n <!-- Schema Info -->\n <div class=\"flex-grow-1\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"mb-0 text-truncate me-2\">\n {{ schema.name }}\n @if (schema.version) {\n <small class=\"text-muted\">v{{ schema.version }}</small>\n }\n </h6>\n\n <div class=\"flex-shrink-0\">\n @if (schema.category) {\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\n {{ schema.category }}\n </span>\n }\n </div>\n </div>\n\n @if (schema.description) {\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\n }\n\n <div class=\"d-flex align-items-center justify-content-between\">\n @if (schema.columnsCount !== undefined) {\n <small class=\"text-muted\">\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\n </small>\n }\n\n @if (schema.url) {\n <a\n [href]=\"schema.url\"\n target=\"_blank\"\n class=\"btn btn-sm btn-outline-secondary\"\n title=\"View Schema Documentation\"\n (click)=\"$event.stopPropagation()\">\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </a>\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-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)=\"onConfirm()\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-arrows-move me-1\"></i>\n @if (selectedSchemaIds().size > 0) {\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\n } @else {\n Reorder with All Schemas\n }\n </button>\n</div>", styles: [".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{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6785
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SchemaSelectionModal, deps: [{ token: i2.NgbActiveModal }, { token: SchemaService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
6786
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SchemaSelectionModal, isStandalone: true, selector: "ccv-schema-selection-modal", ngImport: i0, template: "<!-- Schema Selection Modal -->\n<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Description -->\n <div class=\"alert alert-info mb-4\">\n <div class=\"d-flex align-items-start\">\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\n <div>\n <small>{{ description }}</small>\n <br>\n <small class=\"text-muted\">\n Leave empty to use all available schemas or select specific ones for custom ordering.\n </small>\n </div>\n </div>\n </div>\n\n <!-- Search and Actions -->\n <div class=\"row mb-3\">\n <div class=\"col-md-8\">\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 placeholder=\"Search schemas...\"\n [(ngModel)]=\"searchTerm\"\n [ngModelOptions]=\"{updateOn: 'change'}\"\n >\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"btn-group w-100\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"selectAll()\"\n [disabled]=\"filteredSchemas.length === 0\"\n title=\"Select All Visible\">\n <i class=\"bi bi-check-all\"></i> All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearSelection()\"\n [disabled]=\"selectedSchemaIds().size === 0\"\n title=\"Clear Selection\">\n <i class=\"bi bi-x-circle\"></i> Clear\n </button>\n </div>\n </div>\n </div>\n\n <!-- Selection Summary -->\n @if (selectedSchemaIds().size > 0) {\n <div class=\"alert alert-success mb-3\">\n <i class=\"bi bi-check-circle me-2\"></i>\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\n </div>\n }\n\n <!-- Loading State -->\n @if (isLoading()) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading schemas...</span>\n </div>\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\n </div>\n } @else {\n <!-- Schema List -->\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @if (filteredSchemas.length === 0) {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\n <h6>No schemas found</h6>\n <p class=\"small mb-0\">\n @if (searchTerm()) {\n Try adjusting your search terms\n } @else {\n No schemas are currently available\n }\n </p>\n </div>\n } @else {\n @for (schema of filteredSchemas; track schema.id) {\n <div\n class=\"schema-item border rounded p-3 mb-2\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchemaSelection(schema.id)\"\n role=\"button\">\n\n <div class=\"d-flex align-items-start\">\n <!-- Checkbox -->\n <div class=\"form-check me-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [checked]=\"isSchemaSelected(schema.id)\"\n [id]=\"'schema-' + schema.id\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\n Select {{ schema.name }}\n </label>\n </div>\n\n <!-- Schema Info -->\n <div class=\"flex-grow-1\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"mb-0 text-truncate me-2\">\n {{ schema.name }}\n @if (schema.version) {\n <small class=\"text-muted\">v{{ schema.version }}</small>\n }\n </h6>\n\n <div class=\"flex-shrink-0\">\n @if (schema.category) {\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\n {{ schema.category }}\n </span>\n }\n </div>\n </div>\n\n @if (schema.description) {\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\n }\n\n <div class=\"d-flex align-items-center justify-content-between\">\n @if (schema.columnsCount !== undefined) {\n <small class=\"text-muted\">\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\n </small>\n }\n\n @if (schema.url) {\n <a\n [href]=\"schema.url\"\n target=\"_blank\"\n class=\"btn btn-sm btn-outline-secondary\"\n title=\"View Schema Documentation\"\n (click)=\"$event.stopPropagation()\">\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </a>\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-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)=\"onConfirm()\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-arrows-move me-1\"></i>\n @if (selectedSchemaIds().size > 0) {\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\n } @else {\n Reorder with All Schemas\n }\n </button>\n</div>", styles: [".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{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6655
6787
  }
6656
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SchemaSelectionModal, decorators: [{
6788
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SchemaSelectionModal, decorators: [{
6657
6789
  type: Component,
6658
6790
  args: [{ selector: 'ccv-schema-selection-modal', standalone: true, imports: [CommonModule, FormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Schema Selection Modal -->\n<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Description -->\n <div class=\"alert alert-info mb-4\">\n <div class=\"d-flex align-items-start\">\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\n <div>\n <small>{{ description }}</small>\n <br>\n <small class=\"text-muted\">\n Leave empty to use all available schemas or select specific ones for custom ordering.\n </small>\n </div>\n </div>\n </div>\n\n <!-- Search and Actions -->\n <div class=\"row mb-3\">\n <div class=\"col-md-8\">\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 placeholder=\"Search schemas...\"\n [(ngModel)]=\"searchTerm\"\n [ngModelOptions]=\"{updateOn: 'change'}\"\n >\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"btn-group w-100\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"selectAll()\"\n [disabled]=\"filteredSchemas.length === 0\"\n title=\"Select All Visible\">\n <i class=\"bi bi-check-all\"></i> All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"clearSelection()\"\n [disabled]=\"selectedSchemaIds().size === 0\"\n title=\"Clear Selection\">\n <i class=\"bi bi-x-circle\"></i> Clear\n </button>\n </div>\n </div>\n </div>\n\n <!-- Selection Summary -->\n @if (selectedSchemaIds().size > 0) {\n <div class=\"alert alert-success mb-3\">\n <i class=\"bi bi-check-circle me-2\"></i>\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\n </div>\n }\n\n <!-- Loading State -->\n @if (isLoading()) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading schemas...</span>\n </div>\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\n </div>\n } @else {\n <!-- Schema List -->\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @if (filteredSchemas.length === 0) {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\n <h6>No schemas found</h6>\n <p class=\"small mb-0\">\n @if (searchTerm()) {\n Try adjusting your search terms\n } @else {\n No schemas are currently available\n }\n </p>\n </div>\n } @else {\n @for (schema of filteredSchemas; track schema.id) {\n <div\n class=\"schema-item border rounded p-3 mb-2\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchemaSelection(schema.id)\"\n role=\"button\">\n\n <div class=\"d-flex align-items-start\">\n <!-- Checkbox -->\n <div class=\"form-check me-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [checked]=\"isSchemaSelected(schema.id)\"\n [id]=\"'schema-' + schema.id\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\n Select {{ schema.name }}\n </label>\n </div>\n\n <!-- Schema Info -->\n <div class=\"flex-grow-1\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"mb-0 text-truncate me-2\">\n {{ schema.name }}\n @if (schema.version) {\n <small class=\"text-muted\">v{{ schema.version }}</small>\n }\n </h6>\n\n <div class=\"flex-shrink-0\">\n @if (schema.category) {\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\n {{ schema.category }}\n </span>\n }\n </div>\n </div>\n\n @if (schema.description) {\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\n }\n\n <div class=\"d-flex align-items-center justify-content-between\">\n @if (schema.columnsCount !== undefined) {\n <small class=\"text-muted\">\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\n </small>\n }\n\n @if (schema.url) {\n <a\n [href]=\"schema.url\"\n target=\"_blank\"\n class=\"btn btn-sm btn-outline-secondary\"\n title=\"View Schema Documentation\"\n (click)=\"$event.stopPropagation()\">\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </a>\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-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)=\"onConfirm()\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-arrows-move me-1\"></i>\n @if (selectedSchemaIds().size > 0) {\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\n } @else {\n Reorder with All Schemas\n }\n </button>\n</div>", styles: [".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{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.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"] }]
6659
6791
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }, { type: SchemaService }, { type: i3.ToastService }] });
@@ -6740,10 +6872,10 @@ class TableCreationModalComponent {
6740
6872
  };
6741
6873
  return labels[fieldName] || fieldName;
6742
6874
  }
6743
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableCreationModalComponent, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6744
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: TableCreationModalComponent, isStandalone: true, selector: "ccv-table-creation-modal", inputs: { template: "template", labGroups: "labGroups", selectedLabGroup: "selectedLabGroup" }, 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=\"labGroup\">\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]=\"tableFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6875
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: TableCreationModalComponent, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6876
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: TableCreationModalComponent, isStandalone: true, selector: "ccv-table-creation-modal", inputs: { template: "template", labGroups: "labGroups", selectedLabGroup: "selectedLabGroup" }, 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=\"labGroup\">\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]=\"tableFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6745
6877
  }
6746
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableCreationModalComponent, decorators: [{
6878
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: TableCreationModalComponent, decorators: [{
6747
6879
  type: Component,
6748
6880
  args: [{ selector: 'ccv-table-creation-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, 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=\"labGroup\">\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]=\"tableFormInvalid() || 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"] }]
6749
6881
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { template: [{
@@ -7222,10 +7354,10 @@ class MetadataTableTemplates {
7222
7354
  }
7223
7355
  });
7224
7356
  }
7225
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataTableTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }, { token: i3.ToastService }, { token: AsyncTaskUIService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
7226
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\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-secondary\"\n (click)=\"syncTemplateFromSchemas(template)\"\n title=\"Sync columns from linked schemas\">\n <i class=\"bi bi-arrow-repeat\"></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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7357
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataTableTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }, { token: i3.ToastService }, { token: AsyncTaskUIService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
7358
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\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-secondary\"\n (click)=\"syncTemplateFromSchemas(template)\"\n title=\"Sync columns from linked schemas\">\n <i class=\"bi bi-arrow-repeat\"></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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7227
7359
  }
7228
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableTemplates, decorators: [{
7360
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableTemplates, decorators: [{
7229
7361
  type: Component,
7230
7362
  args: [{ selector: 'ccv-metadata-table-templates', imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\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-secondary\"\n (click)=\"syncTemplateFromSchemas(template)\"\n title=\"Sync columns from linked schemas\">\n <i class=\"bi bi-arrow-repeat\"></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" }]
7231
7363
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataTableTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }, { type: i3.ToastService }, { type: AsyncTaskUIService }, { type: i3.ConfirmDialogService }] });
@@ -7546,10 +7678,10 @@ class ColumnTemplateEditModal {
7546
7678
  this.editForm.get('defaultValue')?.markAsTouched();
7547
7679
  }
7548
7680
  };
7549
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: MetadataColumnTemplateService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
7550
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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\" id=\"columnTemplateModalTitle\">\n <i class=\"bi bi-clipboard-list me-2\" aria-hidden=\"true\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea\n class=\"form-control\"\n id=\"description\"\n formControlName=\"description\"\n rows=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ $safeNavigationMigration(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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i7.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: i1$1.JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7681
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: MetadataColumnTemplateService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
7682
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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\" id=\"columnTemplateModalTitle\">\n <i class=\"bi bi-clipboard-list me-2\" aria-hidden=\"true\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea\n class=\"form-control\"\n id=\"description\"\n formControlName=\"description\"\n rows=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ $safeNavigationMigration(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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i7.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: i1$1.JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7551
7683
  }
7552
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
7684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
7553
7685
  type: Component,
7554
7686
  args: [{ selector: 'ccv-column-template-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\" id=\"columnTemplateModalTitle\">\n <i class=\"bi bi-clipboard-list me-2\" aria-hidden=\"true\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea\n class=\"form-control\"\n id=\"description\"\n formControlName=\"description\"\n rows=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ $safeNavigationMigration(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]=\"editFormInvalid() || 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" }]
7555
7687
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: MetadataColumnTemplateService }, { type: OntologySearchService }], propDecorators: { template: [{
@@ -8162,10 +8294,10 @@ class ColumnTemplates {
8162
8294
  }
8163
8295
  this.expandedGroups.set(expanded);
8164
8296
  }
8165
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: SchemaService }, { token: i2.NgbModal }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
8166
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ColumnTemplates, isStandalone: true, selector: "ccv-column-templates", ngImport: i0, template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n @if (selectedSchema()) {\n <span class=\"badge bg-info ms-2\">{{ selectedSchema()!.displayName || selectedSchema()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"schemaSearch\"\n placeholder=\"Search schemas...\"\n [ngbTypeahead]=\"searchSchemas\"\n [resultFormatter]=\"formatSchema\"\n [inputFormatter]=\"formatSchema\"\n (selectItem)=\"onSchemaSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [(ngModel)]=\"schemaSearchText\"\n [ngModelOptions]=\"{standalone: true}\">\n <label for=\"schemaSearch\">\n <i class=\"bi bi-file-earmark-text me-1\"></i>Schema\n </label>\n </div>\n @if (selectedSchema()) {\n <div class=\"d-flex align-items-center mt-1\">\n <small class=\"text-muted me-2\">Filtering: {{ selectedSchema()!.displayName || selectedSchema()!.name }}</small>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary py-0 px-1\"\n (click)=\"clearSchemaFilter()\"\n title=\"Clear schema filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n <option value=\"global\">Global</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\n </div>\n <div class=\"col-md-4 d-flex align-items-center\">\n <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Schema type filter\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'all' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('all')\">\n All Schemas\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'builtin' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('builtin')\">\n Builtin\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'user' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('user')\">\n User\n </button>\n </div>\n </div>\n </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 flex-wrap gap-2 mb-3\">\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n @if (viewMode() === 'flat') {\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n } @else {\n <span class=\"badge bg-primary ms-2\">{{ groupedTemplatesData().count }} groups</span>\n }\n </h5>\n <div class=\"btn-group btn-group-sm ms-3\" role=\"group\" aria-label=\"View mode\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'flat' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('flat')\"\n title=\"Flat view - show all templates\">\n <i class=\"bi bi-list-ul\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'grouped' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('grouped')\"\n title=\"Grouped view - group by column name\">\n <i class=\"bi bi-collection\"></i>\n </button>\n </div>\n </div>\n <div class=\"d-flex align-items-center gap-2\">\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"expandAllGroups()\"\n title=\"Expand all groups\">\n <i class=\"bi bi-arrows-expand\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"collapseAllGroups()\"\n title=\"Collapse all groups\">\n <i class=\"bi bi-arrows-collapse\"></i>\n </button>\n }\n @if (isLoading() || isLoadingGrouped()) {\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>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <div class=\"input-group input-group-sm\" style=\"width: 300px; max-width: 100%;\">\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 <!-- Flat View -->\n @if (viewMode() === 'flat' && hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th class=\"sortable\"\n (click)=\"sortBy('schema')\"\n [class.sorted]=\"isSorted('schema')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-file-earmark-text me-1\"></i>Schema</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('schema')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.schemaName) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySchema(template)\"\n title=\"Click to filter by this schema\">\n {{ template.schemaName }}\n </span>\n } @else if (template.sourceSchema) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySourceSchema(template.sourceSchema)\"\n title=\"Click to filter by this schema\">\n {{ template.sourceSchema }}\n </span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'flat' && !hasTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value || searchForm.get('schemaId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n\n <!-- Grouped View -->\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th style=\"width: 40px;\"></th>\n <th><i class=\"bi bi-list me-1\"></i>Column Name</th>\n <th><i class=\"bi bi-layers me-1\"></i>Type</th>\n <th><i class=\"bi bi-file-earmark-text me-1\"></i>Schemas</th>\n <th><i class=\"bi bi-hash me-1\"></i>Variants</th>\n </tr>\n </thead>\n <tbody>\n @for (group of groupedTemplatesData().results; track getGroupKey(group)) {\n <tr\n class=\"group-header cursor-pointer\"\n (click)=\"toggleGroup(group)\"\n [class.table-active]=\"isGroupExpanded(group)\">\n <td class=\"text-center\">\n @if (isGroupLoading(group)) {\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n } @else {\n <i class=\"bi\" [class.bi-chevron-down]=\"isGroupExpanded(group)\" [class.bi-chevron-right]=\"!isGroupExpanded(group)\"></i>\n }\n </td>\n <td>\n <strong>{{ group.columnName }}</strong>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ group.columnType }}</span>\n </td>\n <td>\n @for (schema of group.schemas; track schema; let i = $index) {\n @if (i < 3) {\n <span class=\"badge bg-info me-1\">{{ schema }}</span>\n }\n }\n @if (group.schemas.length > 3) {\n <span class=\"badge bg-secondary\">+{{ group.schemas.length - 3 }} more</span>\n }\n </td>\n <td>\n <span class=\"badge bg-primary\">{{ group.schemaCount }}</span>\n </td>\n </tr>\n @if (isGroupExpanded(group)) {\n @for (template of getGroupTemplates(group); track template.id) {\n <tr class=\"group-child\">\n <td></td>\n <td colspan=\"4\">\n <div class=\"d-flex justify-content-between align-items-center ps-3\">\n <div>\n <span class=\"text-muted me-2\">{{ template.name }}</span>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success me-2\" title=\"System template\"></i>\n }\n @if (template.sourceSchema) {\n <span class=\"badge bg-info me-2\">{{ template.sourceSchema }}</span>\n }\n @if (template.ontologyType) {\n <span class=\"badge bg-success me-2\">{{ template.ontologyType }}</span>\n }\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 @case ('global') {\n <span class=\"badge bg-primary\">Global</span>\n }\n }\n </div>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template); $event.stopPropagation()\"\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); $event.stopPropagation()\"\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); $event.stopPropagation()\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </div>\n </td>\n </tr>\n }\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Grouped Pagination -->\n @if (showGroupedPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"groupedTemplatesData().count\"\n [page]=\"groupedCurrentPage()\"\n [pageSize]=\"groupedPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onGroupedPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'grouped' && !hasGroupedTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-collection fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Grouped Templates Found</h5>\n <p class=\"text-muted\">\n Try adjusting your search criteria or switch to flat view.\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".cursor-pointer{cursor:pointer}.cursor-pointer:hover{opacity:.8;text-decoration:underline}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.group-header{cursor:pointer;transition:background-color .15s ease}.group-header:hover{background-color:rgba(var(--bs-primary-rgb),.08)}.group-header .bi-chevron-down,.group-header .bi-chevron-right{transition:transform .15s ease}.group-child{background-color:rgba(var(--bs-secondary-rgb),.05)}.group-child:hover{background-color:rgba(var(--bs-secondary-rgb),.1)}\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i7.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbPaginationModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8297
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: SchemaService }, { token: i2.NgbModal }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
8298
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: ColumnTemplates, isStandalone: true, selector: "ccv-column-templates", ngImport: i0, template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n @if (selectedSchema()) {\n <span class=\"badge bg-info ms-2\">{{ selectedSchema()!.displayName || selectedSchema()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"schemaSearch\"\n placeholder=\"Search schemas...\"\n [ngbTypeahead]=\"searchSchemas\"\n [resultFormatter]=\"formatSchema\"\n [inputFormatter]=\"formatSchema\"\n (selectItem)=\"onSchemaSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [(ngModel)]=\"schemaSearchText\"\n [ngModelOptions]=\"{standalone: true}\">\n <label for=\"schemaSearch\">\n <i class=\"bi bi-file-earmark-text me-1\"></i>Schema\n </label>\n </div>\n @if (selectedSchema()) {\n <div class=\"d-flex align-items-center mt-1\">\n <small class=\"text-muted me-2\">Filtering: {{ selectedSchema()!.displayName || selectedSchema()!.name }}</small>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary py-0 px-1\"\n (click)=\"clearSchemaFilter()\"\n title=\"Clear schema filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n <option value=\"global\">Global</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\n </div>\n <div class=\"col-md-4 d-flex align-items-center\">\n <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Schema type filter\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'all' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('all')\">\n All Schemas\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'builtin' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('builtin')\">\n Builtin\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'user' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('user')\">\n User\n </button>\n </div>\n </div>\n </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 flex-wrap gap-2 mb-3\">\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n @if (viewMode() === 'flat') {\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n } @else {\n <span class=\"badge bg-primary ms-2\">{{ groupedTemplatesData().count }} groups</span>\n }\n </h5>\n <div class=\"btn-group btn-group-sm ms-3\" role=\"group\" aria-label=\"View mode\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'flat' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('flat')\"\n title=\"Flat view - show all templates\">\n <i class=\"bi bi-list-ul\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'grouped' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('grouped')\"\n title=\"Grouped view - group by column name\">\n <i class=\"bi bi-collection\"></i>\n </button>\n </div>\n </div>\n <div class=\"d-flex align-items-center gap-2\">\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"expandAllGroups()\"\n title=\"Expand all groups\">\n <i class=\"bi bi-arrows-expand\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"collapseAllGroups()\"\n title=\"Collapse all groups\">\n <i class=\"bi bi-arrows-collapse\"></i>\n </button>\n }\n @if (isLoading() || isLoadingGrouped()) {\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>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <div class=\"input-group input-group-sm\" style=\"width: 300px; max-width: 100%;\">\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 <!-- Flat View -->\n @if (viewMode() === 'flat' && hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th class=\"sortable\"\n (click)=\"sortBy('schema')\"\n [class.sorted]=\"isSorted('schema')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-file-earmark-text me-1\"></i>Schema</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('schema')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.schemaName) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySchema(template)\"\n title=\"Click to filter by this schema\">\n {{ template.schemaName }}\n </span>\n } @else if (template.sourceSchema) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySourceSchema(template.sourceSchema)\"\n title=\"Click to filter by this schema\">\n {{ template.sourceSchema }}\n </span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'flat' && !hasTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value || searchForm.get('schemaId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n\n <!-- Grouped View -->\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th style=\"width: 40px;\"></th>\n <th><i class=\"bi bi-list me-1\"></i>Column Name</th>\n <th><i class=\"bi bi-layers me-1\"></i>Type</th>\n <th><i class=\"bi bi-file-earmark-text me-1\"></i>Schemas</th>\n <th><i class=\"bi bi-hash me-1\"></i>Variants</th>\n </tr>\n </thead>\n <tbody>\n @for (group of groupedTemplatesData().results; track getGroupKey(group)) {\n <tr\n class=\"group-header cursor-pointer\"\n (click)=\"toggleGroup(group)\"\n [class.table-active]=\"isGroupExpanded(group)\">\n <td class=\"text-center\">\n @if (isGroupLoading(group)) {\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n } @else {\n <i class=\"bi\" [class.bi-chevron-down]=\"isGroupExpanded(group)\" [class.bi-chevron-right]=\"!isGroupExpanded(group)\"></i>\n }\n </td>\n <td>\n <strong>{{ group.columnName }}</strong>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ group.columnType }}</span>\n </td>\n <td>\n @for (schema of group.schemas; track schema; let i = $index) {\n @if (i < 3) {\n <span class=\"badge bg-info me-1\">{{ schema }}</span>\n }\n }\n @if (group.schemas.length > 3) {\n <span class=\"badge bg-secondary\">+{{ group.schemas.length - 3 }} more</span>\n }\n </td>\n <td>\n <span class=\"badge bg-primary\">{{ group.schemaCount }}</span>\n </td>\n </tr>\n @if (isGroupExpanded(group)) {\n @for (template of getGroupTemplates(group); track template.id) {\n <tr class=\"group-child\">\n <td></td>\n <td colspan=\"4\">\n <div class=\"d-flex justify-content-between align-items-center ps-3\">\n <div>\n <span class=\"text-muted me-2\">{{ template.name }}</span>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success me-2\" title=\"System template\"></i>\n }\n @if (template.sourceSchema) {\n <span class=\"badge bg-info me-2\">{{ template.sourceSchema }}</span>\n }\n @if (template.ontologyType) {\n <span class=\"badge bg-success me-2\">{{ template.ontologyType }}</span>\n }\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 @case ('global') {\n <span class=\"badge bg-primary\">Global</span>\n }\n }\n </div>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template); $event.stopPropagation()\"\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); $event.stopPropagation()\"\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); $event.stopPropagation()\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </div>\n </td>\n </tr>\n }\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Grouped Pagination -->\n @if (showGroupedPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"groupedTemplatesData().count\"\n [page]=\"groupedCurrentPage()\"\n [pageSize]=\"groupedPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onGroupedPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'grouped' && !hasGroupedTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-collection fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Grouped Templates Found</h5>\n <p class=\"text-muted\">\n Try adjusting your search criteria or switch to flat view.\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".cursor-pointer{cursor:pointer}.cursor-pointer:hover{opacity:.8;text-decoration:underline}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.group-header{cursor:pointer;transition:background-color .15s ease}.group-header:hover{background-color:rgba(var(--bs-primary-rgb),.08)}.group-header .bi-chevron-down,.group-header .bi-chevron-right{transition:transform .15s ease}.group-child{background-color:rgba(var(--bs-secondary-rgb),.05)}.group-child:hover{background-color:rgba(var(--bs-secondary-rgb),.1)}\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i7.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbPaginationModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8167
8299
  }
8168
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnTemplates, decorators: [{
8300
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnTemplates, decorators: [{
8169
8301
  type: Component,
8170
8302
  args: [{ selector: 'ccv-column-templates', standalone: true, imports: [CommonModule, ReactiveFormsModule, FormsModule, NgbModule, NgbPaginationModule, NgbTypeahead], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n @if (selectedSchema()) {\n <span class=\"badge bg-info ms-2\">{{ selectedSchema()!.displayName || selectedSchema()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"schemaSearch\"\n placeholder=\"Search schemas...\"\n [ngbTypeahead]=\"searchSchemas\"\n [resultFormatter]=\"formatSchema\"\n [inputFormatter]=\"formatSchema\"\n (selectItem)=\"onSchemaSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [(ngModel)]=\"schemaSearchText\"\n [ngModelOptions]=\"{standalone: true}\">\n <label for=\"schemaSearch\">\n <i class=\"bi bi-file-earmark-text me-1\"></i>Schema\n </label>\n </div>\n @if (selectedSchema()) {\n <div class=\"d-flex align-items-center mt-1\">\n <small class=\"text-muted me-2\">Filtering: {{ selectedSchema()!.displayName || selectedSchema()!.name }}</small>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary py-0 px-1\"\n (click)=\"clearSchemaFilter()\"\n title=\"Clear schema filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"visibilityFilter\"\n formControlName=\"visibility\">\n <option [ngValue]=\"null\">All Accessible</option>\n <option value=\"private\">Private (My Templates)</option>\n <option value=\"group\">Lab Group</option>\n <option value=\"public\">Public</option>\n <option value=\"global\">Global</option>\n </select>\n <label for=\"visibilityFilter\">Visibility</label>\n </div>\n </div>\n <div class=\"col-md-4 d-flex align-items-center\">\n <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Schema type filter\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'all' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('all')\">\n All Schemas\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'builtin' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('builtin')\">\n Builtin\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'user' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('user')\">\n User\n </button>\n </div>\n </div>\n </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 flex-wrap gap-2 mb-3\">\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n @if (viewMode() === 'flat') {\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n } @else {\n <span class=\"badge bg-primary ms-2\">{{ groupedTemplatesData().count }} groups</span>\n }\n </h5>\n <div class=\"btn-group btn-group-sm ms-3\" role=\"group\" aria-label=\"View mode\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'flat' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('flat')\"\n title=\"Flat view - show all templates\">\n <i class=\"bi bi-list-ul\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"viewMode() === 'grouped' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"setViewMode('grouped')\"\n title=\"Grouped view - group by column name\">\n <i class=\"bi bi-collection\"></i>\n </button>\n </div>\n </div>\n <div class=\"d-flex align-items-center gap-2\">\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"expandAllGroups()\"\n title=\"Expand all groups\">\n <i class=\"bi bi-arrows-expand\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"collapseAllGroups()\"\n title=\"Collapse all groups\">\n <i class=\"bi bi-arrows-collapse\"></i>\n </button>\n }\n @if (isLoading() || isLoadingGrouped()) {\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>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\n <div class=\"input-group input-group-sm\" style=\"width: 300px; max-width: 100%;\">\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 <!-- Flat View -->\n @if (viewMode() === 'flat' && hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th class=\"sortable\"\n (click)=\"sortBy('schema')\"\n [class.sorted]=\"isSorted('schema')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-file-earmark-text me-1\"></i>Schema</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('schema')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.schemaName) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySchema(template)\"\n title=\"Click to filter by this schema\">\n {{ template.schemaName }}\n </span>\n } @else if (template.sourceSchema) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySourceSchema(template.sourceSchema)\"\n title=\"Click to filter by this schema\">\n {{ template.sourceSchema }}\n </span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'flat' && !hasTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value || searchForm.get('schemaId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n\n <!-- Grouped View -->\n @if (viewMode() === 'grouped' && hasGroupedTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th style=\"width: 40px;\"></th>\n <th><i class=\"bi bi-list me-1\"></i>Column Name</th>\n <th><i class=\"bi bi-layers me-1\"></i>Type</th>\n <th><i class=\"bi bi-file-earmark-text me-1\"></i>Schemas</th>\n <th><i class=\"bi bi-hash me-1\"></i>Variants</th>\n </tr>\n </thead>\n <tbody>\n @for (group of groupedTemplatesData().results; track getGroupKey(group)) {\n <tr\n class=\"group-header cursor-pointer\"\n (click)=\"toggleGroup(group)\"\n [class.table-active]=\"isGroupExpanded(group)\">\n <td class=\"text-center\">\n @if (isGroupLoading(group)) {\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n } @else {\n <i class=\"bi\" [class.bi-chevron-down]=\"isGroupExpanded(group)\" [class.bi-chevron-right]=\"!isGroupExpanded(group)\"></i>\n }\n </td>\n <td>\n <strong>{{ group.columnName }}</strong>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ group.columnType }}</span>\n </td>\n <td>\n @for (schema of group.schemas; track schema; let i = $index) {\n @if (i < 3) {\n <span class=\"badge bg-info me-1\">{{ schema }}</span>\n }\n }\n @if (group.schemas.length > 3) {\n <span class=\"badge bg-secondary\">+{{ group.schemas.length - 3 }} more</span>\n }\n </td>\n <td>\n <span class=\"badge bg-primary\">{{ group.schemaCount }}</span>\n </td>\n </tr>\n @if (isGroupExpanded(group)) {\n @for (template of getGroupTemplates(group); track template.id) {\n <tr class=\"group-child\">\n <td></td>\n <td colspan=\"4\">\n <div class=\"d-flex justify-content-between align-items-center ps-3\">\n <div>\n <span class=\"text-muted me-2\">{{ template.name }}</span>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success me-2\" title=\"System template\"></i>\n }\n @if (template.sourceSchema) {\n <span class=\"badge bg-info me-2\">{{ template.sourceSchema }}</span>\n }\n @if (template.ontologyType) {\n <span class=\"badge bg-success me-2\">{{ template.ontologyType }}</span>\n }\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 @case ('global') {\n <span class=\"badge bg-primary\">Global</span>\n }\n }\n </div>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template); $event.stopPropagation()\"\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); $event.stopPropagation()\"\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); $event.stopPropagation()\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </div>\n </td>\n </tr>\n }\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Grouped Pagination -->\n @if (showGroupedPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"groupedTemplatesData().count\"\n [page]=\"groupedCurrentPage()\"\n [pageSize]=\"groupedPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onGroupedPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else if (viewMode() === 'grouped' && !hasGroupedTemplates()) {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-collection fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Grouped Templates Found</h5>\n <p class=\"text-muted\">\n Try adjusting your search criteria or switch to flat view.\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".cursor-pointer{cursor:pointer}.cursor-pointer:hover{opacity:.8;text-decoration:underline}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.group-header{cursor:pointer;transition:background-color .15s ease}.group-header:hover{background-color:rgba(var(--bs-primary-rgb),.08)}.group-header .bi-chevron-down,.group-header .bi-chevron-right{transition:transform .15s ease}.group-child{background-color:rgba(var(--bs-secondary-rgb),.05)}.group-child:hover{background-color:rgba(var(--bs-secondary-rgb),.1)}\n"] }]
8171
8303
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: SchemaService }, { type: i2.NgbModal }, { type: i3.ConfirmDialogService }] });
@@ -8906,10 +9038,10 @@ class FavoriteManagement {
8906
9038
  return 'bi-star';
8907
9039
  return 'bi-circle';
8908
9040
  }
8909
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavoriteManagement, deps: [], target: i0.ɵɵFactoryTarget.Component });
8910
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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\" role=\"group\" [attr.aria-label]=\"'Actions for ' + favorite.name\">\n @if (canEditFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditModal(favorite)\"\n title=\"Edit favorite\"\n [attr.aria-label]=\"'Edit ' + favorite.name\">\n <i class=\"bi bi-pencil\" aria-hidden=\"true\"></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 [attr.aria-label]=\"'Delete ' + favorite.name\">\n <i class=\"bi bi-trash\" aria-hidden=\"true\"></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);\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"favoriteModalTitle\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\" id=\"favoriteModalTitle\">\n <i class=\"bi bi-heart me-2\" aria-hidden=\"true\"></i>{{ editModalTitle }}\n </h5>\n <button type=\"button\" class=\"btn-close\" aria-label=\"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]=\"editFormInvalid()\">\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i7.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: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9041
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FavoriteManagement, deps: [], target: i0.ɵɵFactoryTarget.Component });
9042
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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\" role=\"group\" [attr.aria-label]=\"'Actions for ' + favorite.name\">\n @if (canEditFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditModal(favorite)\"\n title=\"Edit favorite\"\n [attr.aria-label]=\"'Edit ' + favorite.name\">\n <i class=\"bi bi-pencil\" aria-hidden=\"true\"></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 [attr.aria-label]=\"'Delete ' + favorite.name\">\n <i class=\"bi bi-trash\" aria-hidden=\"true\"></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);\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"favoriteModalTitle\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\" id=\"favoriteModalTitle\">\n <i class=\"bi bi-heart me-2\" aria-hidden=\"true\"></i>{{ editModalTitle }}\n </h5>\n <button type=\"button\" class=\"btn-close\" aria-label=\"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]=\"editFormInvalid()\">\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i7.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: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8911
9043
  }
8912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavoriteManagement, decorators: [{
9044
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FavoriteManagement, decorators: [{
8913
9045
  type: Component,
8914
9046
  args: [{ selector: 'ccv-favorite-management', standalone: true, imports: [
8915
9047
  CommonModule,
@@ -8978,10 +9110,10 @@ class SearchReplaceModal {
8978
9110
  }
8979
9111
  return '';
8980
9112
  }
8981
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SearchReplaceModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
8982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n @if (isTableContext) {\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\n } @else {\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\n }\n </div>\n </div>\n\n @if (isTableContext && config.columns && config.columns.length > 0) {\n <div class=\"col-12 mb-3\">\n <label for=\"columnSelect\" class=\"form-label\">\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\n </label>\n <select\n class=\"form-select\"\n id=\"columnSelect\"\n formControlName=\"columnId\">\n <option [ngValue]=\"null\">All Columns</option>\n @for (column of config.columns; track column.id) {\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"oldValue\"\n formControlName=\"oldValue\"\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\n placeholder=\"Enter value to find\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('oldValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Exact match - this will find all cells with exactly this value.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"newValue\"\n formControlName=\"newValue\"\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\n placeholder=\"Enter replacement value\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('newValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n All matching values will be replaced with this value.\n </div>\n </div>\n\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=\"updatePools\"\n formControlName=\"updatePools\">\n <label class=\"form-check-label\" for=\"updatePools\">\n Update Sample Pools\n </label>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n When enabled, this will also update values in related sample pools.\n </div>\n </div>\n\n <div class=\"col-12\">\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\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]=\"searchReplaceFormInvalid() || isProcessing()\">\n @if (isProcessing()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-arrow-repeat me-1\"></i>\n Replace All\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9113
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SearchReplaceModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9114
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n @if (isTableContext) {\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\n } @else {\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\n }\n </div>\n </div>\n\n @if (isTableContext && config.columns && config.columns.length > 0) {\n <div class=\"col-12 mb-3\">\n <label for=\"columnSelect\" class=\"form-label\">\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\n </label>\n <select\n class=\"form-select\"\n id=\"columnSelect\"\n formControlName=\"columnId\">\n <option [ngValue]=\"null\">All Columns</option>\n @for (column of config.columns; track column.id) {\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"oldValue\"\n formControlName=\"oldValue\"\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\n placeholder=\"Enter value to find\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('oldValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Exact match - this will find all cells with exactly this value.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"newValue\"\n formControlName=\"newValue\"\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\n placeholder=\"Enter replacement value\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('newValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n All matching values will be replaced with this value.\n </div>\n </div>\n\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=\"updatePools\"\n formControlName=\"updatePools\">\n <label class=\"form-check-label\" for=\"updatePools\">\n Update Sample Pools\n </label>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n When enabled, this will also update values in related sample pools.\n </div>\n </div>\n\n <div class=\"col-12\">\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\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]=\"searchReplaceFormInvalid() || isProcessing()\">\n @if (isProcessing()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-arrow-repeat me-1\"></i>\n Replace All\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8983
9115
  }
8984
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SearchReplaceModal, decorators: [{
9116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SearchReplaceModal, decorators: [{
8985
9117
  type: Component,
8986
9118
  args: [{ selector: 'ccv-search-replace-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n @if (isTableContext) {\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\n } @else {\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\n }\n </div>\n </div>\n\n @if (isTableContext && config.columns && config.columns.length > 0) {\n <div class=\"col-12 mb-3\">\n <label for=\"columnSelect\" class=\"form-label\">\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\n </label>\n <select\n class=\"form-select\"\n id=\"columnSelect\"\n formControlName=\"columnId\">\n <option [ngValue]=\"null\">All Columns</option>\n @for (column of config.columns; track column.id) {\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"oldValue\"\n formControlName=\"oldValue\"\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\n placeholder=\"Enter value to find\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('oldValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Exact match - this will find all cells with exactly this value.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"newValue\"\n formControlName=\"newValue\"\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\n placeholder=\"Enter replacement value\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('newValue') }}\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n All matching values will be replaced with this value.\n </div>\n </div>\n\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=\"updatePools\"\n formControlName=\"updatePools\">\n <label class=\"form-check-label\" for=\"updatePools\">\n Update Sample Pools\n </label>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n When enabled, this will also update values in related sample pools.\n </div>\n </div>\n\n <div class=\"col-12\">\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\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]=\"searchReplaceFormInvalid() || isProcessing()\">\n @if (isProcessing()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-arrow-repeat me-1\"></i>\n Replace All\n </button>\n</div>\n" }]
8987
9119
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -9023,10 +9155,10 @@ class SamplePoolDetailsModal {
9023
9155
  const pooledAndIndependent = this.pool?.pooledAndIndependentSamples?.length || 0;
9024
9156
  return pooledOnly + pooledAndIndependent;
9025
9157
  }
9026
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9027
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9028
9160
  }
9029
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
9161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
9030
9162
  type: Component,
9031
9163
  args: [{ selector: 'ccv-sample-pool-details-modal', standalone: true, imports: [CommonModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>" }]
9032
9164
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { pool: [{
@@ -9179,10 +9311,10 @@ class SamplePoolEditModal {
9179
9311
  get totalSamplesCount() {
9180
9312
  return this.pooledOnlySamplesCount + this.pooledAndIndependentSamplesCount;
9181
9313
  }
9182
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
9183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9314
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
9315
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9184
9316
  }
9185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolEditModal, decorators: [{
9317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolEditModal, decorators: [{
9186
9318
  type: Component,
9187
9319
  args: [{ selector: 'ccv-sample-pool-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, 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]=\"editFormInvalid() || 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>" }]
9188
9320
  }], ctorParameters: () => [], propDecorators: { pool: [{
@@ -9533,10 +9665,10 @@ class SamplePoolCreateModal {
9533
9665
  }
9534
9666
  return false;
9535
9667
  }
9536
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
9537
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9668
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
9669
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9538
9670
  }
9539
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
9671
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
9540
9672
  type: Component,
9541
9673
  args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createFormInvalid() || 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" }]
9542
9674
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
@@ -9589,10 +9721,10 @@ class DualScrollDirective {
9589
9721
  this.el.nativeElement.removeEventListener('scroll', this.onHostScroll);
9590
9722
  this.topBar?.remove();
9591
9723
  }
9592
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DualScrollDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
9593
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: DualScrollDirective, isStandalone: true, selector: "[appDualScroll]", ngImport: i0 });
9724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: DualScrollDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
9725
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.1", type: DualScrollDirective, isStandalone: true, selector: "[appDualScroll]", ngImport: i0 });
9594
9726
  }
9595
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DualScrollDirective, decorators: [{
9727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: DualScrollDirective, decorators: [{
9596
9728
  type: Directive,
9597
9729
  args: [{
9598
9730
  selector: '[appDualScroll]',
@@ -9769,7 +9901,7 @@ class MetadataTableDetails {
9769
9901
  ngOnInit() {
9770
9902
  this.route.params.subscribe(params => {
9771
9903
  const id = Number(params['id']);
9772
- const mode = params['mode'] || 'list';
9904
+ const mode = params['mode'] || 'table';
9773
9905
  if (id && !isNaN(id)) {
9774
9906
  this.tableId.set(id);
9775
9907
  this.viewMode.set(mode);
@@ -9789,7 +9921,6 @@ class MetadataTableDetails {
9789
9921
  setupAsyncTaskRefreshListener(tableId) {
9790
9922
  this.asyncTaskService.metadataTableRefresh$.pipe(takeUntil$1(this.destroy$)).subscribe((refreshedTableId) => {
9791
9923
  if (refreshedTableId === tableId) {
9792
- console.log(`Import task completed for table ${refreshedTableId}, refreshing table details`);
9793
9924
  this.toastService.info('Refreshing table data after successful import');
9794
9925
  this.loadTable(tableId);
9795
9926
  }
@@ -9806,7 +9937,6 @@ class MetadataTableDetails {
9806
9937
  },
9807
9938
  error: (error) => {
9808
9939
  this.isLoading.set(false);
9809
- console.error('Error loading metadata table:', error);
9810
9940
  const errorMsg = error?.error?.detail || 'Table not found';
9811
9941
  this.toastService.error(errorMsg);
9812
9942
  if (error.status === 404) {
@@ -9856,11 +9986,9 @@ class MetadataTableDetails {
9856
9986
  });
9857
9987
  }
9858
9988
  triggerFileInput() {
9859
- console.log('triggerFileInput called');
9860
9989
  this.fileInput.nativeElement.click();
9861
9990
  }
9862
9991
  triggerExcelInput() {
9863
- console.log('triggerExcelInput called');
9864
9992
  this.excelInput.nativeElement.click();
9865
9993
  }
9866
9994
  async importSdrf(event) {
@@ -10001,7 +10129,6 @@ class MetadataTableDetails {
10001
10129
  this.toastService.success(`Excel export queued successfully! Task ID: ${response.taskId}`);
10002
10130
  },
10003
10131
  error: (error) => {
10004
- console.error('Error queuing Excel export:', error);
10005
10132
  const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue Excel export';
10006
10133
  this.toastService.error(errorMsg);
10007
10134
  }
@@ -10020,7 +10147,6 @@ class MetadataTableDetails {
10020
10147
  this.toastService.success(`SDRF export queued successfully! Task ID: ${response.taskId}`);
10021
10148
  },
10022
10149
  error: (error) => {
10023
- console.error('Error queuing SDRF export:', error);
10024
10150
  const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue SDRF export';
10025
10151
  this.toastService.error(errorMsg);
10026
10152
  }
@@ -10035,7 +10161,6 @@ class MetadataTableDetails {
10035
10161
  },
10036
10162
  error: (error) => {
10037
10163
  this.isLoading.set(false);
10038
- console.error('Error exporting table:', error);
10039
10164
  const errorMsg = error?.error?.detail || 'Failed to export table';
10040
10165
  this.toastService.error(errorMsg);
10041
10166
  }
@@ -10054,7 +10179,6 @@ class MetadataTableDetails {
10054
10179
  },
10055
10180
  error: (error) => {
10056
10181
  this.isLoading.set(false);
10057
- console.error('Error deleting metadata table:', error);
10058
10182
  const errorMsg = error?.error?.detail || error?.message || 'Failed to delete table. Please try again.';
10059
10183
  this.toastService.error(errorMsg);
10060
10184
  }
@@ -10349,7 +10473,6 @@ class MetadataTableDetails {
10349
10473
  modalRef.componentInstance.onClose();
10350
10474
  },
10351
10475
  error: (error) => {
10352
- console.error('Error updating column value:', error);
10353
10476
  this.toastService.error('Failed to update column value');
10354
10477
  }
10355
10478
  });
@@ -10374,7 +10497,6 @@ class MetadataTableDetails {
10374
10497
  this.toastService.success(`Column "${column.name}" visibility updated successfully!`);
10375
10498
  },
10376
10499
  error: (error) => {
10377
- console.error('Error updating column hidden property:', error);
10378
10500
  this.toastService.error('Failed to update column visibility');
10379
10501
  }
10380
10502
  });
@@ -10408,13 +10530,9 @@ class MetadataTableDetails {
10408
10530
  }
10409
10531
  else {
10410
10532
  this.toastService.warning(`Auto-filled ${response.updatedCount} sample(s), ${response.failedCount} failed for "${column.name}"`);
10411
- if (response.failedUpdates) {
10412
- console.error('Failed updates:', response.failedUpdates);
10413
- }
10414
10533
  }
10415
10534
  },
10416
10535
  error: (error) => {
10417
- console.error('Error auto-filling column values:', error);
10418
10536
  this.toastService.error('Failed to auto-fill column values');
10419
10537
  }
10420
10538
  });
@@ -10509,7 +10627,6 @@ class MetadataTableDetails {
10509
10627
  this.loadTable(currentTable.id);
10510
10628
  },
10511
10629
  error: (error) => {
10512
- console.error('Error updating column settings:', error);
10513
10630
  this.toastService.error('Failed to update column settings');
10514
10631
  }
10515
10632
  });
@@ -10602,7 +10719,6 @@ class MetadataTableDetails {
10602
10719
  modalRef.componentInstance.onClose();
10603
10720
  },
10604
10721
  error: (error) => {
10605
- console.error('Error updating sample value:', error);
10606
10722
  this.toastService.error(`Failed to update sample ${sampleIndex} value`);
10607
10723
  }
10608
10724
  });
@@ -10705,7 +10821,6 @@ class MetadataTableDetails {
10705
10821
  this.loadTable(currentTable.id);
10706
10822
  },
10707
10823
  error: (error) => {
10708
- console.error('Error adding column:', error);
10709
10824
  this.toastService.error('Failed to add column');
10710
10825
  },
10711
10826
  complete: () => {
@@ -10743,14 +10858,12 @@ class MetadataTableDetails {
10743
10858
  this.isLoading.set(true);
10744
10859
  this.metadataTableService.reorderColumnsBySchemaAsync(tableId, schemaIds).subscribe({
10745
10860
  next: (response) => {
10746
- console.log('Column reorder task started:', response);
10747
10861
  this.toastService.success(`Column reorder task queued successfully! Task ID: ${response.taskId}`);
10748
10862
  // Mark task for monitoring and start real-time updates (same pattern as imports)
10749
10863
  this.asyncTaskService.monitorTask(response.taskId);
10750
10864
  this.isLoading.set(false);
10751
10865
  },
10752
10866
  error: (error) => {
10753
- console.error('Error starting column reorder:', error);
10754
10867
  this.toastService.error('Failed to start column reordering');
10755
10868
  this.isLoading.set(false);
10756
10869
  }
@@ -10780,7 +10893,6 @@ class MetadataTableDetails {
10780
10893
  this.table.update(table => table ? { ...table, columns: updatedTableColumns } : null);
10781
10894
  const draggedColumn = dragColumns[event.currentIndex];
10782
10895
  const newPosition = event.currentIndex;
10783
- console.log(`Moving column "${draggedColumn.name}" to position ${newPosition}`);
10784
10896
  this.metadataTableService.reorderColumn(currentTable.id, draggedColumn.id, newPosition).subscribe({
10785
10897
  next: () => {
10786
10898
  this.toastService.success('Column reordered successfully');
@@ -10792,10 +10904,10 @@ class MetadataTableDetails {
10792
10904
  }
10793
10905
  });
10794
10906
  }
10795
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableDetails, deps: [{ token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: MetadataTableService }, { token: AsyncExportService }, { token: ChunkedUploadService }, { token: SamplePoolService }, { token: MetadataColumnService }, { token: i3.ToastService }, { token: AsyncTaskUIService }, { token: i2.NgbModal }, { token: OfficialColumnCacheService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
10796
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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\">\r\n <!-- Header Section with Breadcrumbs -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100 flex-wrap gap-2\">\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <!-- Breadcrumb Navigation -->\r\n <nav aria-label=\"breadcrumb\" class=\"me-3\">\r\n <ol class=\"breadcrumb mb-0\">\r\n <li class=\"breadcrumb-item\">\r\n <a routerLink=\"/metadata-tables\" class=\"text-decoration-none\">\r\n <i class=\"bi bi-table me-1\"></i>Metadata Tables\r\n </a>\r\n </li>\r\n <li class=\"breadcrumb-item active\" aria-current=\"page\">\r\n @if (table()) {\r\n {{ table()!.name }}\r\n } @else {\r\n Table Details\r\n }\r\n </li>\r\n </ol>\r\n </nav>\r\n\r\n <!-- Table Status Badge -->\r\n @if (table()) {\r\n <span class=\"badge ms-2\" [class]=\"getTableStatusClass()\">\r\n {{ getTableStatusBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"d-flex gap-2 flex-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"navigateBack()\"\r\n title=\"Back to Tables\">\r\n <i class=\"bi bi-arrow-left me-1\"></i>Back\r\n </button>\r\n\r\n <!-- View Mode Toggle -->\r\n @if (hasColumns()) {\r\n <div class=\"btn-group\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'list' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToListMode()\"\r\n title=\"Column List View\">\r\n <i class=\"bi bi-list-ul me-1\"></i>List\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'table' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToTableMode()\"\r\n title=\"Table Data View\">\r\n <i class=\"bi bi-table me-1\"></i>Table\r\n </button>\r\n </div>\r\n }\r\n <!-- Import Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <!-- Hidden file inputs -->\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n accept=\".txt,.tsv\"\r\n style=\"display: none;\"\r\n (change)=\"importSdrf($event)\">\r\n <input\r\n #excelInput\r\n type=\"file\"\r\n accept=\".xlsx,.xls\"\r\n style=\"display: none;\"\r\n (change)=\"importExcel($event)\">\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-success btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Import Data\">\r\n <i class=\"bi bi-upload me-1\"></i>Import\r\n </button>\r\n <div class=\"dropdown-menu\" ngbDropdownMenu>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"overrideSampleCountToggle\"\r\n [checked]=\"overrideSampleCount()\"\r\n (change)=\"overrideSampleCount.set(!overrideSampleCount())\">\r\n <label class=\"form-check-label small\" for=\"overrideSampleCountToggle\" title=\"Use the sample count from the imported file instead of the current table count\">Override count</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"applySchemaTemplatesToggle\"\r\n [checked]=\"applySchemaTemplates()\"\r\n (change)=\"applySchemaTemplates.set(!applySchemaTemplates())\">\r\n <label class=\"form-check-label small\" for=\"applySchemaTemplatesToggle\" title=\"Pre-populate empty table columns from the official schema declared in the SDRF file\">Apply schema</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerFileInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Import SDRF File\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerExcelInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Import Excel Template\r\n </a>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"openColumnOverrideImportModal(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Export Dropdown -->\r\n @if (table() && hasColumns()) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-info btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Export Table\">\r\n <i class=\"bi bi-download me-1\"></i>Export\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('sdrf'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Export as SDRF\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('excel'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Export as Excel\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"editTable()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Edit Table\">\r\n <i class=\"bi bi-pencil me-1\"></i>Edit\r\n </button>\r\n }\r\n\r\n <!-- Reorder Columns Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-warning btn-sm\"\r\n (click)=\"reorderColumns()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Reorder Columns by Schema\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>Reorder\r\n </button>\r\n }\r\n\r\n <!-- Create Pool Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"createPool()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Create Sample Pool\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Create Pool\r\n </button>\r\n }\r\n\r\n <!-- More Actions Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-danger btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n aria-label=\"More actions\">\r\n <i class=\"bi bi-three-dots-vertical\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item text-danger\" href=\"#\" (click)=\"deleteTable(); $event.preventDefault()\">\r\n <i class=\"bi bi-trash me-2\"></i>Delete Table\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Loading Spinner -->\r\n @if (isLoading()) {\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"height: 400px;\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Details Content -->\r\n @if (table() && !isLoading()) {\r\n <div class=\"table-details-content p-4\">\r\n\r\n <!-- Table Information Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>Table Information\r\n </h5>\r\n <small class=\"text-muted\">\r\n Created {{ table()!.createdAt | date:'medium' }}\r\n </small>\r\n </div>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"row\">\r\n <div class=\"col-md-8\">\r\n <h3 class=\"fw-bold mb-3\">{{ table()!.name }}</h3>\r\n @if (table()!.description) {\r\n <p class=\"text-muted mb-3\">{{ table()!.description }}</p>\r\n }\r\n <div class=\"row\">\r\n <div class=\"col-md-6\">\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-hash text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Sample Count:</span>\r\n <span class=\"badge bg-primary\">{{ table()!.sampleCount }}</span>\r\n </div>\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-columns-gap text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Columns:</span>\r\n <span class=\"badge bg-info\">{{ table()!.columns?.length || 0 }}</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n @if (table()!.ownerUsername) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-person text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Owner:</span>\r\n <span>{{ table()!.ownerUsername }}</span>\r\n </div>\r\n }\r\n @if (table()!.labGroupName) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-people text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Lab Group:</span>\r\n <span>{{ table()!.labGroupName }}</span>\r\n </div>\r\n }\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-tag text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Version:</span>\r\n <span class=\"badge bg-light text-dark\">{{ table()!.version || 'v1.0' }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 text-end\">\r\n <div class=\"d-flex flex-column gap-2\">\r\n @if (table()!.isLocked) {\r\n <span class=\"badge bg-warning\">\r\n <i class=\"bi bi-lock me-1\"></i>Locked\r\n </span>\r\n }\r\n @if (table()!.isPublished) {\r\n <span class=\"badge bg-success\">\r\n <i class=\"bi bi-cloud-check me-1\"></i>Published\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Sample Pool Table -->\r\n @if (viewMode() === 'table' && hasPools()) {\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-layers me-2\"></i>Sample Pool Table\r\n <span class=\"badge bg-warning ms-2\">{{ sortedPools().length }} pools</span>\r\n </h5>\r\n </div>\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">Pool</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (poolRow of poolTableRows(); track poolRow._poolId) {\r\n <tr class=\"pool-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\" \r\n [class.clickable]=\"table()!.canEdit\"\r\n (click)=\"table()!.canEdit ? editPool(poolRow._poolData) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit pool: ' + poolRow._poolName : poolRow._poolName\">\r\n <i class=\"bi bi-layers me-2\"></i>{{ poolRow._poolName }}\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-pencil-square ms-1 text-primary\" style=\"font-size: 0.8em;\"></i>\r\n }\r\n </td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"pool-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-pool-id]=\"poolRow._poolId\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editPoolColumnValue(poolRow._poolData, column) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for pool ' + poolRow._poolName : ''\">\r\n {{ poolRow['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Columns/Table Section -->\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between flex-wrap gap-2\">\r\n <h5 class=\"mb-0\">\r\n @if (viewMode() === 'list') {\r\n <i class=\"bi bi-list-columns me-2\"></i>Table Columns\r\n } @else {\r\n <i class=\"bi bi-table me-2\"></i>Table Data\r\n }\r\n </h5>\r\n\r\n <!-- Column Filter -->\r\n @if (hasColumns()) {\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <div class=\"input-group input-group-sm\" style=\"width: 250px; max-width: 100%;\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-funnel text-muted\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Filter columns...\"\r\n [value]=\"columnFilter()\"\r\n (input)=\"onColumnFilterChange($any($event.target).value)\">\r\n @if (columnFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary\"\r\n (click)=\"clearColumnFilter()\"\r\n title=\"Clear filter\"\r\n aria-label=\"Clear column filter\">\r\n <i class=\"bi bi-x\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n @if (columnFilter()) {\r\n <small class=\"text-muted ms-2\">\r\n {{ filteredColumnCount }}/{{ totalColumnCount }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n \r\n <div class=\"d-flex gap-2 align-items-center\">\r\n @if (viewMode() === 'list') {\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n @if (table() && table()!.canEdit && sortedColumns().length === 0) {\r\n <small class=\"text-muted\">Import SDRF to add columns</small>\r\n }\r\n } @else {\r\n <span class=\"badge bg-info\">{{ table()!.sampleCount }} samples</span>\r\n @if (hasPools()) {\r\n <span class=\"badge bg-warning\">{{ sortedPools().length }} pools</span>\r\n }\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (hasColumns()) {\r\n <!-- List Mode: Column Definitions -->\r\n @if (viewMode() === 'list') {\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead>\r\n <tr>\r\n <th scope=\"col\" style=\"width: 40px;\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-arrows-move text-muted\" title=\"Drag to reorder\"></i>\r\n }\r\n </th>\r\n <th scope=\"col\" style=\"width: 60px;\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('position')\"\r\n [class.sorted]=\"isSorted('position')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>#</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('position')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('name')\"\r\n [class.sorted]=\"isSorted('name')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Column Name</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('type')\"\r\n [class.sorted]=\"isSorted('type')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Type</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('type')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('value')\"\r\n [class.sorted]=\"isSorted('value')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Value</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('value')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\">Properties</th>\r\n <th scope=\"col\" style=\"width: 100px;\">Actions</th>\r\n </tr>\r\n </thead>\r\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"dragColumnsForList()\">\r\n @for (column of dragColumnsForList(); track column.id) {\r\n <tr cdkDrag [cdkDragData]=\"column\" [cdkDragDisabled]=\"!table()!.canEdit\">\r\n <td class=\"text-center\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\r\n }\r\n </td>\r\n <td class=\"text-muted fw-semibold\">\r\n {{ (column.columnPosition || 0) + 1 }}\r\n </td>\r\n <td>\r\n <div class=\"d-flex align-items-center\">\r\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(column) + ' ' + getColumnTypeClass(column)\"></i>\r\n <span class=\"fw-semibold\">{{ column.name }}</span>\r\n </div>\r\n </td>\r\n <td>\r\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\r\n </td>\r\n <td>\r\n <div>\r\n <!-- Default Value -->\r\n <div class=\"fw-semibold\">{{ getColumnValueDisplay(column) }}</div>\r\n\r\n <!-- Sample-specific Modifiers -->\r\n @if (hasModifiers(column)) {\r\n <div class=\"mt-1\">\r\n @for (modifierDisplay of getModifiersDisplay(column); track $index) {\r\n <small class=\"badge bg-light text-dark me-1 mb-1 d-inline-block\"\r\n style=\"font-size: 0.7rem;\"\r\n [title]=\"modifierDisplay\">\r\n {{ modifierDisplay }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"d-flex gap-1 flex-wrap\">\r\n @if (column.mandatory) {\r\n <span class=\"badge bg-danger\" title=\"Required field\">Required</span>\r\n }\r\n @if (column.readonly) {\r\n <span class=\"badge bg-secondary\" title=\"Read-only field\">Read-only</span>\r\n }\r\n @if (column.hidden) {\r\n <span class=\"badge bg-warning\" title=\"Hidden field\">Hidden</span>\r\n }\r\n @if (column.autoGenerated) {\r\n <span class=\"badge bg-info\" title=\"Auto-generated field\">Auto</span>\r\n }\r\n @if (column.ontologyType) {\r\n <span class=\"badge bg-success\" title=\"Ontology: {{ column.ontologyType }}\">\r\n {{ column.ontologyType }}\r\n </span>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"btn-group btn-group-sm\">\r\n <button type=\"button\" class=\"btn btn-outline-primary\"\r\n (click)=\"viewColumnDetails(column)\"\r\n title=\"View column details\"\r\n [attr.aria-label]=\"'View details for ' + column.name\">\r\n <i class=\"bi bi-info-circle\" aria-hidden=\"true\"></i>\r\n </button>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\"\r\n class=\"btn\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\"\r\n [attr.aria-label]=\"column.hidden ? 'Show ' + column.name : 'Hide ' + column.name\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-success\"\r\n (click)=\"editColumnValue(column)\"\r\n title=\"Edit default value\"\r\n [attr.aria-label]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil-square\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"autofillColumnValues(column)\"\r\n title=\"Auto-fill values\"\r\n [attr.aria-label]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary\"\r\n (click)=\"editColumnSettings(column)\"\r\n title=\"Edit column settings\"\r\n [attr.aria-label]=\"'Edit settings for ' + column.name\">\r\n <i class=\"bi bi-gear\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"openColumnHistory(column)\"\r\n title=\"View column history\"\r\n [attr.aria-label]=\"'View history for ' + column.name\">\r\n <i class=\"bi bi-clock-history\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n <!-- Table Data -->\r\n @if (viewMode() === 'table') {\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">#</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm header-edit-btn me-1\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-info header-edit-btn me-1\"\r\n (click)=\"autofillColumnValues(column)\"\r\n [title]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger header-edit-btn\"\r\n (click)=\"removeColumn(column)\"\r\n [title]=\"'Remove ' + column.name + ' column'\">\r\n <i class=\"bi bi-trash\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of paginatedRows(); track row._sampleIndex) {\r\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"sample-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-sample-index]=\"row._sampleIndex\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\r\n {{ row['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Table Mode Pagination -->\r\n @if (viewMode() === 'table' && totalPages() > 1) {\r\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <span class=\"text-muted\">Rows per page:</span>\r\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\r\n [value]=\"pageSize()\"\r\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\r\n <option value=\"10\">10</option>\r\n <option value=\"25\">25</option>\r\n <option value=\"50\">50</option>\r\n <option value=\"100\">100</option>\r\n </select>\r\n </div>\r\n <ngb-pagination\r\n [collectionSize]=\"table()!.sampleCount\"\r\n [page]=\"currentPage()\"\r\n [pageSize]=\"pageSize()\"\r\n [maxSize]=\"5\"\r\n [boundaryLinks]=\"true\"\r\n [rotate]=\"true\"\r\n (pageChange)=\"onPageChange($event)\"\r\n class=\"pagination-sm mb-0\">\r\n </ngb-pagination>\r\n <span class=\"text-muted\">\r\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), table()!.sampleCount) }}\r\n of {{ table()!.sampleCount }} samples\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"card-body text-center py-5\">\r\n <div class=\"text-muted\">\r\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\r\n <h5>No Columns Found</h5>\r\n <p>This table doesn't have any columns defined yet.</p>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\r\n <i class=\"bi bi-plus me-1\"></i>Add First Column\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\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:var(--cupcake-table-hover);position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover);color:var(--cupcake-text)}.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:var(--cupcake-table-hover);position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover)}.sortable.sorted{background-color:var(--cupcake-bg-secondary)}.sortable.sorted .bi{color:var(--cupcake-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:var(--cupcake-table-hover)}.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$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: DualScrollDirective, selector: "[appDualScroll]" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10907
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableDetails, deps: [{ token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: MetadataTableService }, { token: AsyncExportService }, { token: ChunkedUploadService }, { token: SamplePoolService }, { token: MetadataColumnService }, { token: i3.ToastService }, { token: AsyncTaskUIService }, { token: i2.NgbModal }, { token: OfficialColumnCacheService }, { token: i3.ConfirmDialogService }], target: i0.ɵɵFactoryTarget.Component });
10908
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", 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\">\r\n <!-- Header Section with Breadcrumbs -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100 flex-wrap gap-2\">\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <!-- Breadcrumb Navigation -->\r\n <nav aria-label=\"breadcrumb\" class=\"me-3\">\r\n <ol class=\"breadcrumb mb-0\">\r\n <li class=\"breadcrumb-item\">\r\n <a routerLink=\"/metadata-tables\" class=\"text-decoration-none\">\r\n <i class=\"bi bi-table me-1\"></i>Metadata Tables\r\n </a>\r\n </li>\r\n <li class=\"breadcrumb-item active\" aria-current=\"page\">\r\n @if (table()) {\r\n {{ table()!.name }}\r\n } @else {\r\n Table Details\r\n }\r\n </li>\r\n </ol>\r\n </nav>\r\n\r\n <!-- Table Status Badge -->\r\n @if (table()) {\r\n <span class=\"badge ms-2\" [class]=\"getTableStatusClass()\">\r\n {{ getTableStatusBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"d-flex gap-2 flex-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"navigateBack()\"\r\n title=\"Back to Tables\">\r\n <i class=\"bi bi-arrow-left me-1\"></i>Back\r\n </button>\r\n\r\n <!-- View Mode Toggle -->\r\n @if (hasColumns()) {\r\n <div class=\"btn-group\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'list' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToListMode()\"\r\n title=\"Column List View\">\r\n <i class=\"bi bi-list-ul me-1\"></i>List\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'table' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToTableMode()\"\r\n title=\"Table Data View\">\r\n <i class=\"bi bi-table me-1\"></i>Table\r\n </button>\r\n </div>\r\n }\r\n <!-- Import Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <!-- Hidden file inputs -->\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n accept=\".txt,.tsv\"\r\n style=\"display: none;\"\r\n (change)=\"importSdrf($event)\">\r\n <input\r\n #excelInput\r\n type=\"file\"\r\n accept=\".xlsx,.xls\"\r\n style=\"display: none;\"\r\n (change)=\"importExcel($event)\">\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-success btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Import Data\">\r\n <i class=\"bi bi-upload me-1\"></i>Import\r\n </button>\r\n <div class=\"dropdown-menu\" ngbDropdownMenu>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"overrideSampleCountToggle\"\r\n [checked]=\"overrideSampleCount()\"\r\n (change)=\"overrideSampleCount.set(!overrideSampleCount())\">\r\n <label class=\"form-check-label small\" for=\"overrideSampleCountToggle\" title=\"Use the sample count from the imported file instead of the current table count\">Override count</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"applySchemaTemplatesToggle\"\r\n [checked]=\"applySchemaTemplates()\"\r\n (change)=\"applySchemaTemplates.set(!applySchemaTemplates())\">\r\n <label class=\"form-check-label small\" for=\"applySchemaTemplatesToggle\" title=\"Pre-populate empty table columns from the official schema declared in the SDRF file\">Apply schema</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerFileInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Import SDRF File\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerExcelInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Import Excel Template\r\n </a>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"openColumnOverrideImportModal(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Export Dropdown -->\r\n @if (table() && hasColumns()) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-info btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Export Table\">\r\n <i class=\"bi bi-download me-1\"></i>Export\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('sdrf'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Export as SDRF\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('excel'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Export as Excel\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"editTable()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Edit Table\">\r\n <i class=\"bi bi-pencil me-1\"></i>Edit\r\n </button>\r\n }\r\n\r\n <!-- Reorder Columns Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-warning btn-sm\"\r\n (click)=\"reorderColumns()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Reorder Columns by Schema\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>Reorder\r\n </button>\r\n }\r\n\r\n <!-- Create Pool Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"createPool()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Create Sample Pool\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Create Pool\r\n </button>\r\n }\r\n\r\n <!-- More Actions Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-danger btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n aria-label=\"More actions\">\r\n <i class=\"bi bi-three-dots-vertical\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item text-danger\" href=\"#\" (click)=\"deleteTable(); $event.preventDefault()\">\r\n <i class=\"bi bi-trash me-2\"></i>Delete Table\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Loading Spinner -->\r\n @if (isLoading()) {\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"height: 400px;\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Details Content -->\r\n @if (table() && !isLoading()) {\r\n <div class=\"table-details-content p-4\">\r\n\r\n <!-- Table Information Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>Table Information\r\n </h5>\r\n <small class=\"text-muted\">\r\n Created {{ table()!.createdAt | date:'medium' }}\r\n </small>\r\n </div>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"row\">\r\n <div class=\"col-md-8\">\r\n <h3 class=\"fw-bold mb-3\">{{ table()!.name }}</h3>\r\n @if (table()!.description) {\r\n <p class=\"text-muted mb-3\">{{ table()!.description }}</p>\r\n }\r\n <div class=\"row\">\r\n <div class=\"col-md-6\">\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-hash text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Sample Count:</span>\r\n <span class=\"badge bg-primary\">{{ table()!.sampleCount }}</span>\r\n </div>\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-columns-gap text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Columns:</span>\r\n <span class=\"badge bg-info\">{{ table()!.columns?.length || 0 }}</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n @if (table()!.ownerUsername) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-person text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Owner:</span>\r\n <span>{{ table()!.ownerUsername }}</span>\r\n </div>\r\n }\r\n @if (table()!.labGroupName) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-people text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Lab Group:</span>\r\n <span>{{ table()!.labGroupName }}</span>\r\n </div>\r\n }\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-tag text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Version:</span>\r\n <span class=\"badge bg-light text-dark\">{{ table()!.version || 'v1.0' }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 text-end\">\r\n <div class=\"d-flex flex-column gap-2\">\r\n @if (table()!.isLocked) {\r\n <span class=\"badge bg-warning\">\r\n <i class=\"bi bi-lock me-1\"></i>Locked\r\n </span>\r\n }\r\n @if (table()!.isPublished) {\r\n <span class=\"badge bg-success\">\r\n <i class=\"bi bi-cloud-check me-1\"></i>Published\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Sample Pool Table -->\r\n @if (viewMode() === 'table' && hasPools()) {\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-layers me-2\"></i>Sample Pool Table\r\n <span class=\"badge bg-warning ms-2\">{{ sortedPools().length }} pools</span>\r\n </h5>\r\n </div>\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">Pool</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (poolRow of poolTableRows(); track poolRow._poolId) {\r\n <tr class=\"pool-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\" \r\n [class.clickable]=\"table()!.canEdit\"\r\n (click)=\"table()!.canEdit ? editPool(poolRow._poolData) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit pool: ' + poolRow._poolName : poolRow._poolName\">\r\n <i class=\"bi bi-layers me-2\"></i>{{ poolRow._poolName }}\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-pencil-square ms-1 text-primary\" style=\"font-size: 0.8em;\"></i>\r\n }\r\n </td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"pool-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-pool-id]=\"poolRow._poolId\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editPoolColumnValue(poolRow._poolData, column) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for pool ' + poolRow._poolName : ''\">\r\n {{ poolRow['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Columns/Table Section -->\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between flex-wrap gap-2\">\r\n <h5 class=\"mb-0\">\r\n @if (viewMode() === 'list') {\r\n <i class=\"bi bi-list-columns me-2\"></i>Table Columns\r\n } @else {\r\n <i class=\"bi bi-table me-2\"></i>Table Data\r\n }\r\n </h5>\r\n\r\n <!-- Column Filter -->\r\n @if (hasColumns()) {\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <div class=\"input-group input-group-sm\" style=\"width: 250px; max-width: 100%;\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-funnel text-muted\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Filter columns...\"\r\n [value]=\"columnFilter()\"\r\n (input)=\"onColumnFilterChange($any($event.target).value)\">\r\n @if (columnFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary\"\r\n (click)=\"clearColumnFilter()\"\r\n title=\"Clear filter\"\r\n aria-label=\"Clear column filter\">\r\n <i class=\"bi bi-x\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n @if (columnFilter()) {\r\n <small class=\"text-muted ms-2\">\r\n {{ filteredColumnCount }}/{{ totalColumnCount }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n \r\n <div class=\"d-flex gap-2 align-items-center\">\r\n @if (viewMode() === 'list') {\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n @if (table() && table()!.canEdit && sortedColumns().length === 0) {\r\n <small class=\"text-muted\">Import SDRF to add columns</small>\r\n }\r\n } @else {\r\n <span class=\"badge bg-info\">{{ table()!.sampleCount }} samples</span>\r\n @if (hasPools()) {\r\n <span class=\"badge bg-warning\">{{ sortedPools().length }} pools</span>\r\n }\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (hasColumns()) {\r\n <!-- List Mode: Column Definitions -->\r\n @if (viewMode() === 'list') {\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead>\r\n <tr>\r\n <th scope=\"col\" style=\"width: 40px;\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-arrows-move text-muted\" title=\"Drag to reorder\"></i>\r\n }\r\n </th>\r\n <th scope=\"col\" style=\"width: 60px;\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('position')\"\r\n [class.sorted]=\"isSorted('position')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>#</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('position')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('name')\"\r\n [class.sorted]=\"isSorted('name')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Column Name</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('type')\"\r\n [class.sorted]=\"isSorted('type')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Type</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('type')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('value')\"\r\n [class.sorted]=\"isSorted('value')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Value</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('value')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\">Properties</th>\r\n <th scope=\"col\" style=\"width: 100px;\">Actions</th>\r\n </tr>\r\n </thead>\r\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"dragColumnsForList()\">\r\n @for (column of dragColumnsForList(); track column.id) {\r\n <tr cdkDrag [cdkDragData]=\"column\" [cdkDragDisabled]=\"!table()!.canEdit\">\r\n <td class=\"text-center\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\r\n }\r\n </td>\r\n <td class=\"text-muted fw-semibold\">\r\n {{ (column.columnPosition || 0) + 1 }}\r\n </td>\r\n <td>\r\n <div class=\"d-flex align-items-center\">\r\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(column) + ' ' + getColumnTypeClass(column)\"></i>\r\n <span class=\"fw-semibold\">{{ column.name }}</span>\r\n </div>\r\n </td>\r\n <td>\r\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\r\n </td>\r\n <td>\r\n <div>\r\n <!-- Default Value -->\r\n <div class=\"fw-semibold\">{{ getColumnValueDisplay(column) }}</div>\r\n\r\n <!-- Sample-specific Modifiers -->\r\n @if (hasModifiers(column)) {\r\n <div class=\"mt-1\">\r\n @for (modifierDisplay of getModifiersDisplay(column); track $index) {\r\n <small class=\"badge bg-light text-dark me-1 mb-1 d-inline-block\"\r\n style=\"font-size: 0.7rem;\"\r\n [title]=\"modifierDisplay\">\r\n {{ modifierDisplay }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"d-flex gap-1 flex-wrap\">\r\n @if (column.mandatory) {\r\n <span class=\"badge bg-danger\" title=\"Required field\">Required</span>\r\n }\r\n @if (column.readonly) {\r\n <span class=\"badge bg-secondary\" title=\"Read-only field\">Read-only</span>\r\n }\r\n @if (column.hidden) {\r\n <span class=\"badge bg-warning\" title=\"Hidden field\">Hidden</span>\r\n }\r\n @if (column.autoGenerated) {\r\n <span class=\"badge bg-info\" title=\"Auto-generated field\">Auto</span>\r\n }\r\n @if (column.ontologyType) {\r\n <span class=\"badge bg-success\" title=\"Ontology: {{ column.ontologyType }}\">\r\n {{ column.ontologyType }}\r\n </span>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"btn-group btn-group-sm\">\r\n <button type=\"button\" class=\"btn btn-outline-primary\"\r\n (click)=\"viewColumnDetails(column)\"\r\n title=\"View column details\"\r\n [attr.aria-label]=\"'View details for ' + column.name\">\r\n <i class=\"bi bi-info-circle\" aria-hidden=\"true\"></i>\r\n </button>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\"\r\n class=\"btn\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\"\r\n [attr.aria-label]=\"column.hidden ? 'Show ' + column.name : 'Hide ' + column.name\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-success\"\r\n (click)=\"editColumnValue(column)\"\r\n title=\"Edit default value\"\r\n [attr.aria-label]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil-square\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"autofillColumnValues(column)\"\r\n title=\"Auto-fill values\"\r\n [attr.aria-label]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary\"\r\n (click)=\"editColumnSettings(column)\"\r\n title=\"Edit column settings\"\r\n [attr.aria-label]=\"'Edit settings for ' + column.name\">\r\n <i class=\"bi bi-gear\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"openColumnHistory(column)\"\r\n title=\"View column history\"\r\n [attr.aria-label]=\"'View history for ' + column.name\">\r\n <i class=\"bi bi-clock-history\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n <!-- Table Data -->\r\n @if (viewMode() === 'table') {\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">#</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm header-edit-btn me-1\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-info header-edit-btn me-1\"\r\n (click)=\"autofillColumnValues(column)\"\r\n [title]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger header-edit-btn\"\r\n (click)=\"removeColumn(column)\"\r\n [title]=\"'Remove ' + column.name + ' column'\">\r\n <i class=\"bi bi-trash\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of paginatedRows(); track row._sampleIndex) {\r\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"sample-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-sample-index]=\"row._sampleIndex\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\r\n {{ row['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Table Mode Pagination -->\r\n @if (viewMode() === 'table' && totalPages() > 1) {\r\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <span class=\"text-muted\">Rows per page:</span>\r\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\r\n [value]=\"pageSize()\"\r\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\r\n <option value=\"10\">10</option>\r\n <option value=\"25\">25</option>\r\n <option value=\"50\">50</option>\r\n <option value=\"100\">100</option>\r\n </select>\r\n </div>\r\n <ngb-pagination\r\n [collectionSize]=\"table()!.sampleCount\"\r\n [page]=\"currentPage()\"\r\n [pageSize]=\"pageSize()\"\r\n [maxSize]=\"5\"\r\n [boundaryLinks]=\"true\"\r\n [rotate]=\"true\"\r\n (pageChange)=\"onPageChange($event)\"\r\n class=\"pagination-sm mb-0\">\r\n </ngb-pagination>\r\n <span class=\"text-muted\">\r\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), table()!.sampleCount) }}\r\n of {{ table()!.sampleCount }} samples\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"card-body text-center py-5\">\r\n <div class=\"text-muted\">\r\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\r\n <h5>No Columns Found</h5>\r\n <p>This table doesn't have any columns defined yet.</p>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\r\n <i class=\"bi bi-plus me-1\"></i>Add First Column\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\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:var(--cupcake-table-hover);position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover);color:var(--cupcake-text)}.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:var(--cupcake-table-hover);position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover)}.sortable.sorted{background-color:var(--cupcake-bg-secondary)}.sortable.sorted .bi{color:var(--cupcake-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:var(--cupcake-table-hover)}.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$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: DualScrollDirective, selector: "[appDualScroll]" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10797
10909
  }
10798
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataTableDetails, decorators: [{
10910
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataTableDetails, decorators: [{
10799
10911
  type: Component,
10800
10912
  args: [{ selector: 'ccv-metadata-table-details', standalone: true, imports: [CommonModule, RouterModule, NgbModule, DragDropModule, DualScrollDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"metadata-table-details-container\">\r\n <!-- Header Section with Breadcrumbs -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100 flex-wrap gap-2\">\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <!-- Breadcrumb Navigation -->\r\n <nav aria-label=\"breadcrumb\" class=\"me-3\">\r\n <ol class=\"breadcrumb mb-0\">\r\n <li class=\"breadcrumb-item\">\r\n <a routerLink=\"/metadata-tables\" class=\"text-decoration-none\">\r\n <i class=\"bi bi-table me-1\"></i>Metadata Tables\r\n </a>\r\n </li>\r\n <li class=\"breadcrumb-item active\" aria-current=\"page\">\r\n @if (table()) {\r\n {{ table()!.name }}\r\n } @else {\r\n Table Details\r\n }\r\n </li>\r\n </ol>\r\n </nav>\r\n\r\n <!-- Table Status Badge -->\r\n @if (table()) {\r\n <span class=\"badge ms-2\" [class]=\"getTableStatusClass()\">\r\n {{ getTableStatusBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"d-flex gap-2 flex-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"navigateBack()\"\r\n title=\"Back to Tables\">\r\n <i class=\"bi bi-arrow-left me-1\"></i>Back\r\n </button>\r\n\r\n <!-- View Mode Toggle -->\r\n @if (hasColumns()) {\r\n <div class=\"btn-group\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'list' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToListMode()\"\r\n title=\"Column List View\">\r\n <i class=\"bi bi-list-ul me-1\"></i>List\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm\"\r\n [class]=\"viewMode() === 'table' ? 'btn-primary' : 'btn-outline-primary'\"\r\n (click)=\"switchToTableMode()\"\r\n title=\"Table Data View\">\r\n <i class=\"bi bi-table me-1\"></i>Table\r\n </button>\r\n </div>\r\n }\r\n <!-- Import Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <!-- Hidden file inputs -->\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n accept=\".txt,.tsv\"\r\n style=\"display: none;\"\r\n (change)=\"importSdrf($event)\">\r\n <input\r\n #excelInput\r\n type=\"file\"\r\n accept=\".xlsx,.xls\"\r\n style=\"display: none;\"\r\n (change)=\"importExcel($event)\">\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-success btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Import Data\">\r\n <i class=\"bi bi-upload me-1\"></i>Import\r\n </button>\r\n <div class=\"dropdown-menu\" ngbDropdownMenu>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"overrideSampleCountToggle\"\r\n [checked]=\"overrideSampleCount()\"\r\n (change)=\"overrideSampleCount.set(!overrideSampleCount())\">\r\n <label class=\"form-check-label small\" for=\"overrideSampleCountToggle\" title=\"Use the sample count from the imported file instead of the current table count\">Override count</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-item\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"applySchemaTemplatesToggle\"\r\n [checked]=\"applySchemaTemplates()\"\r\n (change)=\"applySchemaTemplates.set(!applySchemaTemplates())\">\r\n <label class=\"form-check-label small\" for=\"applySchemaTemplatesToggle\" title=\"Pre-populate empty table columns from the official schema declared in the SDRF file\">Apply schema</label>\r\n </div>\r\n </div>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerFileInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Import SDRF File\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerExcelInput(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Import Excel Template\r\n </a>\r\n <div class=\"dropdown-divider\"></div>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"openColumnOverrideImportModal(); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Export Dropdown -->\r\n @if (table() && hasColumns()) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-info btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n title=\"Export Table\">\r\n <i class=\"bi bi-download me-1\"></i>Export\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('sdrf'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-text me-2\"></i>Export as SDRF\r\n </a>\r\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('excel'); $event.preventDefault()\">\r\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Export as Excel\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"editTable()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Edit Table\">\r\n <i class=\"bi bi-pencil me-1\"></i>Edit\r\n </button>\r\n }\r\n\r\n <!-- Reorder Columns Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-warning btn-sm\"\r\n (click)=\"reorderColumns()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Reorder Columns by Schema\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>Reorder\r\n </button>\r\n }\r\n\r\n <!-- Create Pool Button -->\r\n @if (table() && table()!.canEdit && hasColumns()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"createPool()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Create Sample Pool\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Create Pool\r\n </button>\r\n }\r\n\r\n <!-- More Actions Dropdown -->\r\n @if (table() && table()!.canEdit) {\r\n <div class=\"btn-group\" ngbDropdown>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-danger btn-sm dropdown-toggle\"\r\n ngbDropdownToggle\r\n [disabled]=\"isLoading()\"\r\n aria-label=\"More actions\">\r\n <i class=\"bi bi-three-dots-vertical\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\r\n <a class=\"dropdown-item text-danger\" href=\"#\" (click)=\"deleteTable(); $event.preventDefault()\">\r\n <i class=\"bi bi-trash me-2\"></i>Delete Table\r\n </a>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Loading Spinner -->\r\n @if (isLoading()) {\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"height: 400px;\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Details Content -->\r\n @if (table() && !isLoading()) {\r\n <div class=\"table-details-content p-4\">\r\n\r\n <!-- Table Information Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>Table Information\r\n </h5>\r\n <small class=\"text-muted\">\r\n Created {{ table()!.createdAt | date:'medium' }}\r\n </small>\r\n </div>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"row\">\r\n <div class=\"col-md-8\">\r\n <h3 class=\"fw-bold mb-3\">{{ table()!.name }}</h3>\r\n @if (table()!.description) {\r\n <p class=\"text-muted mb-3\">{{ table()!.description }}</p>\r\n }\r\n <div class=\"row\">\r\n <div class=\"col-md-6\">\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-hash text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Sample Count:</span>\r\n <span class=\"badge bg-primary\">{{ table()!.sampleCount }}</span>\r\n </div>\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-columns-gap text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Columns:</span>\r\n <span class=\"badge bg-info\">{{ table()!.columns?.length || 0 }}</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n @if (table()!.ownerUsername) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-person text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Owner:</span>\r\n <span>{{ table()!.ownerUsername }}</span>\r\n </div>\r\n }\r\n @if (table()!.labGroupName) {\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-people text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Lab Group:</span>\r\n <span>{{ table()!.labGroupName }}</span>\r\n </div>\r\n }\r\n <div class=\"d-flex align-items-center mb-2\">\r\n <i class=\"bi bi-tag text-muted me-2\"></i>\r\n <span class=\"fw-semibold me-2\">Version:</span>\r\n <span class=\"badge bg-light text-dark\">{{ table()!.version || 'v1.0' }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 text-end\">\r\n <div class=\"d-flex flex-column gap-2\">\r\n @if (table()!.isLocked) {\r\n <span class=\"badge bg-warning\">\r\n <i class=\"bi bi-lock me-1\"></i>Locked\r\n </span>\r\n }\r\n @if (table()!.isPublished) {\r\n <span class=\"badge bg-success\">\r\n <i class=\"bi bi-cloud-check me-1\"></i>Published\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Sample Pool Table -->\r\n @if (viewMode() === 'table' && hasPools()) {\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-layers me-2\"></i>Sample Pool Table\r\n <span class=\"badge bg-warning ms-2\">{{ sortedPools().length }} pools</span>\r\n </h5>\r\n </div>\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">Pool</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (poolRow of poolTableRows(); track poolRow._poolId) {\r\n <tr class=\"pool-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\" \r\n [class.clickable]=\"table()!.canEdit\"\r\n (click)=\"table()!.canEdit ? editPool(poolRow._poolData) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit pool: ' + poolRow._poolName : poolRow._poolName\">\r\n <i class=\"bi bi-layers me-2\"></i>{{ poolRow._poolName }}\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-pencil-square ms-1 text-primary\" style=\"font-size: 0.8em;\"></i>\r\n }\r\n </td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"pool-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-pool-id]=\"poolRow._poolId\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editPoolColumnValue(poolRow._poolData, column) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for pool ' + poolRow._poolName : ''\">\r\n {{ poolRow['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Columns/Table Section -->\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <div class=\"d-flex align-items-center justify-content-between flex-wrap gap-2\">\r\n <h5 class=\"mb-0\">\r\n @if (viewMode() === 'list') {\r\n <i class=\"bi bi-list-columns me-2\"></i>Table Columns\r\n } @else {\r\n <i class=\"bi bi-table me-2\"></i>Table Data\r\n }\r\n </h5>\r\n\r\n <!-- Column Filter -->\r\n @if (hasColumns()) {\r\n <div class=\"d-flex align-items-center flex-wrap gap-2\">\r\n <div class=\"input-group input-group-sm\" style=\"width: 250px; max-width: 100%;\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-funnel text-muted\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Filter columns...\"\r\n [value]=\"columnFilter()\"\r\n (input)=\"onColumnFilterChange($any($event.target).value)\">\r\n @if (columnFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary\"\r\n (click)=\"clearColumnFilter()\"\r\n title=\"Clear filter\"\r\n aria-label=\"Clear column filter\">\r\n <i class=\"bi bi-x\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n @if (columnFilter()) {\r\n <small class=\"text-muted ms-2\">\r\n {{ filteredColumnCount }}/{{ totalColumnCount }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n \r\n <div class=\"d-flex gap-2 align-items-center\">\r\n @if (viewMode() === 'list') {\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n @if (table() && table()!.canEdit && sortedColumns().length === 0) {\r\n <small class=\"text-muted\">Import SDRF to add columns</small>\r\n }\r\n } @else {\r\n <span class=\"badge bg-info\">{{ table()!.sampleCount }} samples</span>\r\n @if (hasPools()) {\r\n <span class=\"badge bg-warning\">{{ sortedPools().length }} pools</span>\r\n }\r\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\r\n @if (table() && table()!.canEdit) {\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-success btn-sm\"\r\n (click)=\"openAddColumnModal()\"\r\n [disabled]=\"isLoading()\"\r\n title=\"Add new column with automatic reordering\">\r\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (hasColumns()) {\r\n <!-- List Mode: Column Definitions -->\r\n @if (viewMode() === 'list') {\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead>\r\n <tr>\r\n <th scope=\"col\" style=\"width: 40px;\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-arrows-move text-muted\" title=\"Drag to reorder\"></i>\r\n }\r\n </th>\r\n <th scope=\"col\" style=\"width: 60px;\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('position')\"\r\n [class.sorted]=\"isSorted('position')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>#</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('position')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('name')\"\r\n [class.sorted]=\"isSorted('name')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Column Name</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('type')\"\r\n [class.sorted]=\"isSorted('type')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Type</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('type')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\" \r\n class=\"sortable\" \r\n (click)=\"sortBy('value')\"\r\n [class.sorted]=\"isSorted('value')\">\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span>Value</span>\r\n <i class=\"bi ms-1\" [class]=\"getSortIcon('value')\"></i>\r\n </div>\r\n </th>\r\n <th scope=\"col\">Properties</th>\r\n <th scope=\"col\" style=\"width: 100px;\">Actions</th>\r\n </tr>\r\n </thead>\r\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"dragColumnsForList()\">\r\n @for (column of dragColumnsForList(); track column.id) {\r\n <tr cdkDrag [cdkDragData]=\"column\" [cdkDragDisabled]=\"!table()!.canEdit\">\r\n <td class=\"text-center\">\r\n @if (table()!.canEdit) {\r\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\r\n }\r\n </td>\r\n <td class=\"text-muted fw-semibold\">\r\n {{ (column.columnPosition || 0) + 1 }}\r\n </td>\r\n <td>\r\n <div class=\"d-flex align-items-center\">\r\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(column) + ' ' + getColumnTypeClass(column)\"></i>\r\n <span class=\"fw-semibold\">{{ column.name }}</span>\r\n </div>\r\n </td>\r\n <td>\r\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\r\n </td>\r\n <td>\r\n <div>\r\n <!-- Default Value -->\r\n <div class=\"fw-semibold\">{{ getColumnValueDisplay(column) }}</div>\r\n\r\n <!-- Sample-specific Modifiers -->\r\n @if (hasModifiers(column)) {\r\n <div class=\"mt-1\">\r\n @for (modifierDisplay of getModifiersDisplay(column); track $index) {\r\n <small class=\"badge bg-light text-dark me-1 mb-1 d-inline-block\"\r\n style=\"font-size: 0.7rem;\"\r\n [title]=\"modifierDisplay\">\r\n {{ modifierDisplay }}\r\n </small>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"d-flex gap-1 flex-wrap\">\r\n @if (column.mandatory) {\r\n <span class=\"badge bg-danger\" title=\"Required field\">Required</span>\r\n }\r\n @if (column.readonly) {\r\n <span class=\"badge bg-secondary\" title=\"Read-only field\">Read-only</span>\r\n }\r\n @if (column.hidden) {\r\n <span class=\"badge bg-warning\" title=\"Hidden field\">Hidden</span>\r\n }\r\n @if (column.autoGenerated) {\r\n <span class=\"badge bg-info\" title=\"Auto-generated field\">Auto</span>\r\n }\r\n @if (column.ontologyType) {\r\n <span class=\"badge bg-success\" title=\"Ontology: {{ column.ontologyType }}\">\r\n {{ column.ontologyType }}\r\n </span>\r\n }\r\n </div>\r\n </td>\r\n <td>\r\n <div class=\"btn-group btn-group-sm\">\r\n <button type=\"button\" class=\"btn btn-outline-primary\"\r\n (click)=\"viewColumnDetails(column)\"\r\n title=\"View column details\"\r\n [attr.aria-label]=\"'View details for ' + column.name\">\r\n <i class=\"bi bi-info-circle\" aria-hidden=\"true\"></i>\r\n </button>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\"\r\n class=\"btn\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\"\r\n [attr.aria-label]=\"column.hidden ? 'Show ' + column.name : 'Hide ' + column.name\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-success\"\r\n (click)=\"editColumnValue(column)\"\r\n title=\"Edit default value\"\r\n [attr.aria-label]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil-square\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"autofillColumnValues(column)\"\r\n title=\"Auto-fill values\"\r\n [attr.aria-label]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary\"\r\n (click)=\"editColumnSettings(column)\"\r\n title=\"Edit column settings\"\r\n [attr.aria-label]=\"'Edit settings for ' + column.name\">\r\n <i class=\"bi bi-gear\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-info\"\r\n (click)=\"openColumnHistory(column)\"\r\n title=\"View column history\"\r\n [attr.aria-label]=\"'View history for ' + column.name\">\r\n <i class=\"bi bi-clock-history\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n <!-- Table Data -->\r\n @if (viewMode() === 'table') {\r\n <div class=\"card-body p-0\">\r\n <div class=\"table-responsive\" appDualScroll>\r\n <table class=\"table table-hover mb-0\">\r\n <thead class=\"table-light\">\r\n <tr>\r\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">#</th>\r\n @for (column of sortedColumns(); track column.id) {\r\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\r\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\r\n <span>{{ getShortColumnName(column) }}</span>\r\n @if (table()!.canEdit) {\r\n <div class=\"header-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm header-edit-btn me-1\"\r\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\r\n (click)=\"toggleColumnHidden(column)\"\r\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\r\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\r\n (click)=\"editColumnValue(column)\"\r\n [title]=\"'Edit default value for ' + column.name\">\r\n <i class=\"bi bi-pencil\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-info header-edit-btn me-1\"\r\n (click)=\"autofillColumnValues(column)\"\r\n [title]=\"'Auto-fill values for ' + column.name\">\r\n <i class=\"bi bi-magic\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger header-edit-btn\"\r\n (click)=\"removeColumn(column)\"\r\n [title]=\"'Remove ' + column.name + ' column'\">\r\n <i class=\"bi bi-trash\"></i>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of paginatedRows(); track row._sampleIndex) {\r\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\r\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\r\n @for (column of sortedColumns(); track column.id) {\r\n <td\r\n class=\"sample-cell\"\r\n [class.opacity-50]=\"column.hidden\"\r\n [attr.data-sample-index]=\"row._sampleIndex\"\r\n [attr.data-column-id]=\"column.id\"\r\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\r\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\r\n {{ row['col_' + column.id] || '-' }}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Table Mode Pagination -->\r\n @if (viewMode() === 'table' && totalPages() > 1) {\r\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <span class=\"text-muted\">Rows per page:</span>\r\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\r\n [value]=\"pageSize()\"\r\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\r\n <option value=\"10\">10</option>\r\n <option value=\"25\">25</option>\r\n <option value=\"50\">50</option>\r\n <option value=\"100\">100</option>\r\n </select>\r\n </div>\r\n <ngb-pagination\r\n [collectionSize]=\"table()!.sampleCount\"\r\n [page]=\"currentPage()\"\r\n [pageSize]=\"pageSize()\"\r\n [maxSize]=\"5\"\r\n [boundaryLinks]=\"true\"\r\n [rotate]=\"true\"\r\n (pageChange)=\"onPageChange($event)\"\r\n class=\"pagination-sm mb-0\">\r\n </ngb-pagination>\r\n <span class=\"text-muted\">\r\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), table()!.sampleCount) }}\r\n of {{ table()!.sampleCount }} samples\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"card-body text-center py-5\">\r\n <div class=\"text-muted\">\r\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\r\n <h5>No Columns Found</h5>\r\n <p>This table doesn't have any columns defined yet.</p>\r\n @if (table()!.canEdit) {\r\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\r\n <i class=\"bi bi-plus me-1\"></i>Add First Column\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\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:var(--cupcake-table-hover);position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover);color:var(--cupcake-text)}.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:var(--cupcake-table-hover);position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:var(--cupcake-text-muted);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:var(--cupcake-table-hover)}.sortable.sorted{background-color:var(--cupcake-bg-secondary)}.sortable.sorted .bi{color:var(--cupcake-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:var(--cupcake-table-hover)}.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"] }]
10801
10913
  }], ctorParameters: () => [{ type: i1$2.ActivatedRoute }, { type: i1$2.Router }, { type: MetadataTableService }, { type: AsyncExportService }, { type: ChunkedUploadService }, { type: SamplePoolService }, { type: MetadataColumnService }, { type: i3.ToastService }, { type: AsyncTaskUIService }, { type: i2.NgbModal }, { type: OfficialColumnCacheService }, { type: i3.ConfirmDialogService }], propDecorators: { fileInput: [{
@@ -10984,10 +11096,10 @@ class BasicAutofillComponent {
10984
11096
  return !!(field && field.invalid && field.touched);
10985
11097
  }
10986
11098
  Math = Math;
10987
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BasicAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
10988
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: BasicAutofillComponent, isStandalone: true, selector: "ccv-basic-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbPaginationModule }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11099
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: BasicAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
11100
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: BasicAutofillComponent, isStandalone: true, selector: "ccv-basic-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbPaginationModule }, { kind: "component", type: i7$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10989
11101
  }
10990
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BasicAutofillComponent, decorators: [{
11102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: BasicAutofillComponent, decorators: [{
10991
11103
  type: Component,
10992
11104
  args: [{ selector: 'ccv-basic-autofill', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbPaginationModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n" }]
10993
11105
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -11138,10 +11250,10 @@ class AdvancedAutofillComponent {
11138
11250
  };
11139
11251
  this.submit.emit(request);
11140
11252
  }
11141
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AdvancedAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
11142
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AdvancedAutofillComponent, isStandalone: true, selector: "ccv-advanced-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AdvancedAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
11254
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: AdvancedAutofillComponent, isStandalone: true, selector: "ccv-advanced-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11143
11255
  }
11144
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AdvancedAutofillComponent, decorators: [{
11256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: AdvancedAutofillComponent, decorators: [{
11145
11257
  type: Component,
11146
11258
  args: [{ selector: 'ccv-advanced-autofill', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n" }]
11147
11259
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -11190,10 +11302,10 @@ class MetadataColumnAutofillModal {
11190
11302
  close() {
11191
11303
  this.activeModal.dismiss('cancel');
11192
11304
  }
11193
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
11194
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbNavModule }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "component", type: BasicAutofillComponent, selector: "ccv-basic-autofill", inputs: ["config"], outputs: ["submit"] }, { kind: "component", type: AdvancedAutofillComponent, selector: "ccv-advanced-autofill", inputs: ["config"], outputs: ["submit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11305
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
11306
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbNavModule }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "component", type: BasicAutofillComponent, selector: "ccv-basic-autofill", inputs: ["config"], outputs: ["submit"] }, { kind: "component", type: AdvancedAutofillComponent, selector: "ccv-advanced-autofill", inputs: ["config"], outputs: ["submit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11195
11307
  }
11196
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
11308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
11197
11309
  type: Component,
11198
11310
  args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, NgbNavModule, BasicAutofillComponent, AdvancedAutofillComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"] }]
11199
11311
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -11286,10 +11398,10 @@ class MetadataColumnHistoryModal {
11286
11398
  default: return 'badge bg-secondary';
11287
11399
  }
11288
11400
  }
11289
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnHistoryModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11290
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MetadataColumnHistoryModal, isStandalone: true, selector: "ccv-metadata-column-history-modal", inputs: { config: "config" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\n </div>\n }\n\n @if (loading() && history().length === 0) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"mt-2 text-muted\">Loading history...</p>\n </div>\n }\n\n @if (history().length > 0) {\n <div class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Showing {{ history().length }} of {{ totalCount() }} records\n </div>\n\n <div class=\"timeline\">\n @for (record of history(); track record.historyId) {\n <div class=\"timeline-item mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div>\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\n {{ record.historyType }}\n </span>\n <small class=\"text-muted ms-2\">\n {{ record.historyDate | date:'short' }}\n </small>\n </div>\n @if (record.historyUser) {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\n </span>\n }\n </div>\n\n @if (record.changes.length > 0) {\n <div class=\"card\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">Changes:</h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Old Value</th>\n <th>New Value</th>\n </tr>\n </thead>\n <tbody>\n @for (change of record.changes; track change.field) {\n <tr>\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\n <td>\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\n </td>\n <td>\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"alert alert-info mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Initial record - no changes\n </div>\n }\n\n <details class=\"mt-2\">\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\n </summary>\n <div class=\"card mt-2\">\n <div class=\"card-body\">\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\n </div>\n </div>\n </details>\n </div>\n }\n </div>\n\n @if (hasMore()) {\n <div class=\"text-center mt-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"loadMore()\"\n [disabled]=\"loading()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n Load More\n </button>\n </div>\n }\n }\n\n @if (!loading() && history().length === 0 && !error()) {\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No history records found for this column.\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11401
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnHistoryModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11402
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: MetadataColumnHistoryModal, isStandalone: true, selector: "ccv-metadata-column-history-modal", inputs: { config: "config" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\n </div>\n }\n\n @if (loading() && history().length === 0) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"mt-2 text-muted\">Loading history...</p>\n </div>\n }\n\n @if (history().length > 0) {\n <div class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Showing {{ history().length }} of {{ totalCount() }} records\n </div>\n\n <div class=\"timeline\">\n @for (record of history(); track record.historyId) {\n <div class=\"timeline-item mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div>\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\n {{ record.historyType }}\n </span>\n <small class=\"text-muted ms-2\">\n {{ record.historyDate | date:'short' }}\n </small>\n </div>\n @if (record.historyUser) {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\n </span>\n }\n </div>\n\n @if (record.changes.length > 0) {\n <div class=\"card\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">Changes:</h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Old Value</th>\n <th>New Value</th>\n </tr>\n </thead>\n <tbody>\n @for (change of record.changes; track change.field) {\n <tr>\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\n <td>\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\n </td>\n <td>\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"alert alert-info mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Initial record - no changes\n </div>\n }\n\n <details class=\"mt-2\">\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\n </summary>\n <div class=\"card mt-2\">\n <div class=\"card-body\">\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\n </div>\n </div>\n </details>\n </div>\n }\n </div>\n\n @if (hasMore()) {\n <div class=\"text-center mt-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"loadMore()\"\n [disabled]=\"loading()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n Load More\n </button>\n </div>\n }\n }\n\n @if (!loading() && history().length === 0 && !error()) {\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No history records found for this column.\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11291
11403
  }
11292
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnHistoryModal, decorators: [{
11404
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: MetadataColumnHistoryModal, decorators: [{
11293
11405
  type: Component,
11294
11406
  args: [{ selector: 'ccv-metadata-column-history-modal', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\n </div>\n }\n\n @if (loading() && history().length === 0) {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"mt-2 text-muted\">Loading history...</p>\n </div>\n }\n\n @if (history().length > 0) {\n <div class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Showing {{ history().length }} of {{ totalCount() }} records\n </div>\n\n <div class=\"timeline\">\n @for (record of history(); track record.historyId) {\n <div class=\"timeline-item mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div>\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\n {{ record.historyType }}\n </span>\n <small class=\"text-muted ms-2\">\n {{ record.historyDate | date:'short' }}\n </small>\n </div>\n @if (record.historyUser) {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\n </span>\n }\n </div>\n\n @if (record.changes.length > 0) {\n <div class=\"card\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">Changes:</h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Old Value</th>\n <th>New Value</th>\n </tr>\n </thead>\n <tbody>\n @for (change of record.changes; track change.field) {\n <tr>\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\n <td>\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\n </td>\n <td>\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"alert alert-info mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Initial record - no changes\n </div>\n }\n\n <details class=\"mt-2\">\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\n </summary>\n <div class=\"card mt-2\">\n <div class=\"card-body\">\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\n </div>\n </div>\n </details>\n </div>\n }\n </div>\n\n @if (hasMore()) {\n <div class=\"text-center mt-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"loadMore()\"\n [disabled]=\"loading()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n Load More\n </button>\n </div>\n }\n }\n\n @if (!loading() && history().length === 0 && !error()) {\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No history records found for this column.\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"] }]
11295
11407
  }], propDecorators: { config: [{
@@ -11358,10 +11470,10 @@ class OpenInExcel {
11358
11470
  const diffMins = Math.ceil(code.expiresIn / 60);
11359
11471
  return `${diffMins} minute${diffMins !== 1 ? 's' : ''}`;
11360
11472
  }
11361
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OpenInExcel, deps: [], target: i0.ɵɵFactoryTarget.Component });
11362
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: OpenInExcel, isStandalone: true, selector: "ccv-open-in-excel", inputs: { tableId: { classPropertyName: "tableId", publicName: "tableId", isSignal: true, isRequired: true, transformFunction: null }, tableName: { classPropertyName: "tableName", publicName: "tableName", isSignal: true, isRequired: false, transformFunction: null }, buttonClass: { classPropertyName: "buttonClass", publicName: "buttonClass", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, buttonText: { classPropertyName: "buttonText", publicName: "buttonText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { launched: "launched", error: "error" }, ngImport: i0, template: "<button\n type=\"button\"\n [class]=\"buttonClass()\"\n [disabled]=\"isLoading()\"\n (click)=\"createLaunchCode()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n } @else if (showIcon()) {\n <i class=\"bi bi-file-earmark-spreadsheet me-1\"></i>\n }\n {{ buttonText() }}\n</button>\n\n@if (showModal()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Open in Excel\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <p>Use this code in the Excel add-in to open the table:</p>\n\n <div class=\"launch-code-display\">\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control form-control-sm font-monospace\"\n [value]=\"$safeNavigationMigration(launchCode()?.code)\"\n readonly>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"copyCode()\"\n [title]=\"copySuccess() ? 'Copied!' : 'Copy to clipboard'\">\n @if (copySuccess()) {\n <i class=\"bi bi-check text-success\"></i>\n } @else {\n <i class=\"bi bi-clipboard\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"mt-3\">\n <h6>Instructions:</h6>\n <ol class=\"small text-muted mb-0\">\n <li>Open Microsoft Excel</li>\n <li>Open the Cupcake Vanilla add-in</li>\n <li>Click \"Use Launch Code\" on the login screen</li>\n <li>Enter the code above</li>\n </ol>\n </div>\n\n @if (launchCode()?.expiresIn) {\n <div class=\"alert alert-warning mt-3 mb-0 py-2 small\">\n <i class=\"bi bi-clock me-1\"></i>\n This code expires in {{ getExpiryTime() }}\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">Close</button>\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [".launch-code-display{background:var(--bs-tertiary-bg, #f8f9fa);padding:.75rem;border-radius:.375rem;border:1px solid var(--bs-border-color)}.modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1050}.modal{z-index:1055}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.Eager });
11473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OpenInExcel, deps: [], target: i0.ɵɵFactoryTarget.Component });
11474
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: OpenInExcel, isStandalone: true, selector: "ccv-open-in-excel", inputs: { tableId: { classPropertyName: "tableId", publicName: "tableId", isSignal: true, isRequired: true, transformFunction: null }, tableName: { classPropertyName: "tableName", publicName: "tableName", isSignal: true, isRequired: false, transformFunction: null }, buttonClass: { classPropertyName: "buttonClass", publicName: "buttonClass", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, buttonText: { classPropertyName: "buttonText", publicName: "buttonText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { launched: "launched", error: "error" }, ngImport: i0, template: "<button\n type=\"button\"\n [class]=\"buttonClass()\"\n [disabled]=\"isLoading()\"\n (click)=\"createLaunchCode()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n } @else if (showIcon()) {\n <i class=\"bi bi-file-earmark-spreadsheet me-1\"></i>\n }\n {{ buttonText() }}\n</button>\n\n@if (showModal()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Open in Excel\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <p>Use this code in the Excel add-in to open the table:</p>\n\n <div class=\"launch-code-display\">\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control form-control-sm font-monospace\"\n [value]=\"$safeNavigationMigration(launchCode()?.code)\"\n readonly>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"copyCode()\"\n [title]=\"copySuccess() ? 'Copied!' : 'Copy to clipboard'\">\n @if (copySuccess()) {\n <i class=\"bi bi-check text-success\"></i>\n } @else {\n <i class=\"bi bi-clipboard\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"mt-3\">\n <h6>Instructions:</h6>\n <ol class=\"small text-muted mb-0\">\n <li>Open Microsoft Excel</li>\n <li>Open the Cupcake Vanilla add-in</li>\n <li>Click \"Use Launch Code\" on the login screen</li>\n <li>Enter the code above</li>\n </ol>\n </div>\n\n @if (launchCode()?.expiresIn) {\n <div class=\"alert alert-warning mt-3 mb-0 py-2 small\">\n <i class=\"bi bi-clock me-1\"></i>\n This code expires in {{ getExpiryTime() }}\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">Close</button>\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [".launch-code-display{background:var(--bs-tertiary-bg, #f8f9fa);padding:.75rem;border-radius:.375rem;border:1px solid var(--bs-border-color)}.modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1050}.modal{z-index:1055}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.Eager });
11363
11475
  }
11364
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OpenInExcel, decorators: [{
11476
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: OpenInExcel, decorators: [{
11365
11477
  type: Component,
11366
11478
  args: [{ selector: 'ccv-open-in-excel', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.Eager, template: "<button\n type=\"button\"\n [class]=\"buttonClass()\"\n [disabled]=\"isLoading()\"\n (click)=\"createLaunchCode()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n } @else if (showIcon()) {\n <i class=\"bi bi-file-earmark-spreadsheet me-1\"></i>\n }\n {{ buttonText() }}\n</button>\n\n@if (showModal()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Open in Excel\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <p>Use this code in the Excel add-in to open the table:</p>\n\n <div class=\"launch-code-display\">\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control form-control-sm font-monospace\"\n [value]=\"$safeNavigationMigration(launchCode()?.code)\"\n readonly>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"copyCode()\"\n [title]=\"copySuccess() ? 'Copied!' : 'Copy to clipboard'\">\n @if (copySuccess()) {\n <i class=\"bi bi-check text-success\"></i>\n } @else {\n <i class=\"bi bi-clipboard\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"mt-3\">\n <h6>Instructions:</h6>\n <ol class=\"small text-muted mb-0\">\n <li>Open Microsoft Excel</li>\n <li>Open the Cupcake Vanilla add-in</li>\n <li>Click \"Use Launch Code\" on the login screen</li>\n <li>Enter the code above</li>\n </ol>\n </div>\n\n @if (launchCode()?.expiresIn) {\n <div class=\"alert alert-warning mt-3 mb-0 py-2 small\">\n <i class=\"bi bi-clock me-1\"></i>\n This code expires in {{ getExpiryTime() }}\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">Close</button>\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [".launch-code-display{background:var(--bs-tertiary-bg, #f8f9fa);padding:.75rem;border-radius:.375rem;border:1px solid var(--bs-border-color)}.modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1050}.modal{z-index:1055}\n"] }]
11367
11479
  }], propDecorators: { tableId: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableId", required: true }] }], tableName: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableName", required: false }] }], buttonClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonClass", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], buttonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonText", required: false }] }], launched: [{ type: i0.Output, args: ["launched"] }], error: [{ type: i0.Output, args: ["error"] }] } });
@@ -11532,10 +11644,10 @@ class ColumnOverrideImportModal {
11532
11644
  normalizeOntology: v.normalizeOntology,
11533
11645
  };
11534
11646
  }
11535
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnOverrideImportModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ColumnOverrideImportModal, isStandalone: true, selector: "ccv-column-override-import-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\n @if (config) {\n <small class=\"text-muted ms-2 fw-normal\">{{ config.tableName }}</small>\n }\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger py-2 small\">{{ error() }}</div>\n }\n\n @switch (step()) {\n\n @case ('upload') {\n <form [formGroup]=\"optionsForm\">\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">File</label>\n <input\n type=\"file\"\n class=\"form-control form-control-sm\"\n accept=\".tsv,.txt,.xlsx,.xls,.csv\"\n (change)=\"onFileChange($event)\">\n <div class=\"form-text\">SDRF (.tsv), Excel (.xlsx), or CSV</div>\n </div>\n\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">Initial mapping algorithm</label>\n <select class=\"form-select form-select-sm\" formControlName=\"algorithm\">\n <option value=\"name\">By name \u2014 match headers to columns with the same name</option>\n <option value=\"position\">By position \u2014 match by column index order</option>\n <option value=\"none\">None \u2014 map everything manually</option>\n </select>\n <div class=\"form-text\">Duplicate names on either side are always left unmatched for manual resolution.</div>\n </div>\n\n <div class=\"mb-3 d-flex flex-column gap-2\">\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateValue\" formControlName=\"updateValue\">\n <label class=\"form-check-label\" for=\"updateValue\">Update default value</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateModifiers\" formControlName=\"updateModifiers\">\n <label class=\"form-check-label\" for=\"updateModifiers\">Update per-sample values</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"normalizeOntology\" formControlName=\"normalizeOntology\">\n <label class=\"form-check-label\" for=\"normalizeOntology\">Normalise ontology terms on commit</label>\n </div>\n </div>\n </form>\n }\n\n @case ('map') {\n @let result = suggestResult()!;\n\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <small class=\"text-muted\">\n {{ result.fileHeaders.length }} file columns &middot; {{ result.tableColumns.length }} table columns\n @if (newColumnCount() > 0) {\n &middot; <span class=\"text-warning fw-medium\">{{ newColumnCount() }} will be created</span>\n }\n </small>\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [formControl]=\"$any(optionsForm.get('algorithm'))\">\n <option value=\"name\">By name</option>\n <option value=\"position\">By position</option>\n <option value=\"none\">None</option>\n </select>\n <button class=\"btn btn-outline-secondary btn-sm\" [disabled]=\"isLoading()\" (click)=\"reapplyAlgorithm()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm\"></span>\n } @else {\n <i class=\"bi bi-arrow-clockwise\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>File column</th>\n <th>Maps to</th>\n <th class=\"text-center\" style=\"width:60px\">Ignore</th>\n </tr>\n </thead>\n <tbody>\n @for (fh of result.fileHeaders; track fh.fileColIndex) {\n <tr [class.table-warning]=\"!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null'\"\n [class.text-muted]=\"isIgnored(fh.fileColIndex)\">\n <td>\n {{ fh.header }}\n @if (!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"This column does not exist and will be created\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>new\n </span>\n }\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"badge bg-secondary ms-1\">ignored</span>\n }\n </td>\n <td>\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"text-muted fst-italic\">\u2014</span>\n } @else {\n <select\n class=\"form-select form-select-sm\"\n [value]=\"getColumnIdForFileCol(fh.fileColIndex)\"\n (change)=\"updateMappingEntry(fh.fileColIndex, $any($event.target).value)\">\n <option value=\"null\">Create new column</option>\n <optgroup label=\"Existing table columns\">\n @for (tc of result.tableColumns; track tc.columnId) {\n <option\n [value]=\"tc.columnId\"\n [disabled]=\"mappedColumnIds().has(tc.columnId) && getColumnIdForFileCol(fh.fileColIndex) !== tc.columnId.toString()\">\n {{ tc.columnName }}\n </option>\n }\n </optgroup>\n </select>\n }\n </td>\n <td class=\"text-center\">\n @if (isIgnored(fh.fileColIndex)) {\n <button class=\"btn btn-link btn-sm p-0 text-primary\" (click)=\"restoreMappingEntry(fh.fileColIndex)\" title=\"Include\">\n <i class=\"bi bi-plus-circle\"></i>\n </button>\n } @else {\n <button class=\"btn btn-link btn-sm p-0 text-secondary\" (click)=\"removeMappingEntry(fh.fileColIndex)\" title=\"Ignore this column\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('preview') {\n @let result = previewResult()!;\n\n @if (result.sampleCountMismatch) {\n <div class=\"alert alert-warning py-2 small mb-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n @for (w of result.warnings; track $index) { {{ w }} }\n </div>\n }\n\n @if (result.columnsMatched.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1\">\n <i class=\"bi bi-pencil me-1\"></i>Columns to update\n <span class=\"badge bg-primary ms-1\">{{ result.columnsMatched.length }}</span>\n </div>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>Column</th>\n <th>Current value</th>\n <th>New value</th>\n <th class=\"text-center\">Modifiers</th>\n <th class=\"text-center\">Rows</th>\n </tr>\n </thead>\n <tbody>\n @for (col of result.columnsMatched; track col.columnId) {\n <tr [class.table-warning]=\"col.currentValue !== col.newValue && col.newValue !== null\">\n <td class=\"text-nowrap\">{{ col.columnName }}</td>\n <td class=\"text-muted text-truncate\" style=\"max-width:140px\">{{ col.currentValue ?? '\u2014' }}</td>\n <td class=\"text-truncate\" style=\"max-width:140px\">{{ col.newValue ?? '\u2014' }}</td>\n <td class=\"text-center\">\n @if (col.newModifiers.length > 0) {\n <span class=\"badge bg-info\">{{ col.newModifiers.length }}</span>\n } @else { \u2014 }\n </td>\n <td class=\"text-center\">{{ col.rowsChanged }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n @if (result.columnsToAdd.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1 text-warning\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Columns that will be created\n <span class=\"badge bg-warning text-dark ms-1\">{{ result.columnsToAdd.length }}</span>\n </div>\n <ul class=\"list-unstyled small mb-0\">\n @for (col of result.columnsToAdd; track col.fileColIndex) {\n <li><i class=\"bi bi-plus me-1 text-warning\"></i>{{ col.fileHeader }}</li>\n }\n </ul>\n </div>\n }\n\n @if (result.columnsMatched.length === 0 && result.columnsToAdd.length === 0) {\n <div class=\"text-center text-muted py-3 small\">\n <i class=\"bi bi-inbox d-block fs-4 mb-1\"></i>No columns would be changed.\n </div>\n }\n }\n\n @case ('confirming') {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border spinner-border-sm text-primary\"></div>\n <small class=\"d-block mt-2 text-muted\">Applying override...</small>\n </div>\n }\n\n @case ('done') {\n @let result = commitResult()!;\n <div class=\"alert alert-success py-2 small\">\n <i class=\"bi bi-check-circle me-1\"></i>{{ result.message }}\n </div>\n <ul class=\"list-unstyled small mb-0\">\n <li><strong>{{ result.columnsUpdated }}</strong> columns updated</li>\n @if (result.columnsAdded > 0) {\n <li><strong>{{ result.columnsAdded }}</strong> columns created</li>\n }\n </ul>\n }\n\n }\n</div>\n\n<div class=\"modal-footer\">\n @switch (step()) {\n @case ('upload') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"dismiss()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!selectedFile() || isLoading()\"\n (click)=\"generateMapping()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Next: Map Columns\n </button>\n }\n\n @case ('map') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToUpload()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"userMapping().length === 0 || isLoading()\"\n (click)=\"preview()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Preview Changes\n </button>\n }\n\n @case ('preview') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToMap()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-warning btn-sm\"\n [disabled]=\"previewResult()!.columnsMatched.length === 0 && previewResult()!.columnsToAdd.length === 0\"\n (click)=\"confirm()\">\n Confirm Import\n </button>\n }\n\n @case ('done') {\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"close()\">Close</button>\n }\n }\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.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11647
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnOverrideImportModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11648
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: ColumnOverrideImportModal, isStandalone: true, selector: "ccv-column-override-import-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\n @if (config) {\n <small class=\"text-muted ms-2 fw-normal\">{{ config.tableName }}</small>\n }\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger py-2 small\">{{ error() }}</div>\n }\n\n @switch (step()) {\n\n @case ('upload') {\n <form [formGroup]=\"optionsForm\">\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">File</label>\n <input\n type=\"file\"\n class=\"form-control form-control-sm\"\n accept=\".tsv,.txt,.xlsx,.xls,.csv\"\n (change)=\"onFileChange($event)\">\n <div class=\"form-text\">SDRF (.tsv), Excel (.xlsx), or CSV</div>\n </div>\n\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">Initial mapping algorithm</label>\n <select class=\"form-select form-select-sm\" formControlName=\"algorithm\">\n <option value=\"name\">By name \u2014 match headers to columns with the same name</option>\n <option value=\"position\">By position \u2014 match by column index order</option>\n <option value=\"none\">None \u2014 map everything manually</option>\n </select>\n <div class=\"form-text\">Duplicate names on either side are always left unmatched for manual resolution.</div>\n </div>\n\n <div class=\"mb-3 d-flex flex-column gap-2\">\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateValue\" formControlName=\"updateValue\">\n <label class=\"form-check-label\" for=\"updateValue\">Update default value</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateModifiers\" formControlName=\"updateModifiers\">\n <label class=\"form-check-label\" for=\"updateModifiers\">Update per-sample values</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"normalizeOntology\" formControlName=\"normalizeOntology\">\n <label class=\"form-check-label\" for=\"normalizeOntology\">Normalise ontology terms on commit</label>\n </div>\n </div>\n </form>\n }\n\n @case ('map') {\n @let result = suggestResult()!;\n\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <small class=\"text-muted\">\n {{ result.fileHeaders.length }} file columns &middot; {{ result.tableColumns.length }} table columns\n @if (newColumnCount() > 0) {\n &middot; <span class=\"text-warning fw-medium\">{{ newColumnCount() }} will be created</span>\n }\n </small>\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [formControl]=\"$any(optionsForm.get('algorithm'))\">\n <option value=\"name\">By name</option>\n <option value=\"position\">By position</option>\n <option value=\"none\">None</option>\n </select>\n <button class=\"btn btn-outline-secondary btn-sm\" [disabled]=\"isLoading()\" (click)=\"reapplyAlgorithm()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm\"></span>\n } @else {\n <i class=\"bi bi-arrow-clockwise\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>File column</th>\n <th>Maps to</th>\n <th class=\"text-center\" style=\"width:60px\">Ignore</th>\n </tr>\n </thead>\n <tbody>\n @for (fh of result.fileHeaders; track fh.fileColIndex) {\n <tr [class.table-warning]=\"!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null'\"\n [class.text-muted]=\"isIgnored(fh.fileColIndex)\">\n <td>\n {{ fh.header }}\n @if (!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"This column does not exist and will be created\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>new\n </span>\n }\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"badge bg-secondary ms-1\">ignored</span>\n }\n </td>\n <td>\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"text-muted fst-italic\">\u2014</span>\n } @else {\n <select\n class=\"form-select form-select-sm\"\n [value]=\"getColumnIdForFileCol(fh.fileColIndex)\"\n (change)=\"updateMappingEntry(fh.fileColIndex, $any($event.target).value)\">\n <option value=\"null\">Create new column</option>\n <optgroup label=\"Existing table columns\">\n @for (tc of result.tableColumns; track tc.columnId) {\n <option\n [value]=\"tc.columnId\"\n [disabled]=\"mappedColumnIds().has(tc.columnId) && getColumnIdForFileCol(fh.fileColIndex) !== tc.columnId.toString()\">\n {{ tc.columnName }}\n </option>\n }\n </optgroup>\n </select>\n }\n </td>\n <td class=\"text-center\">\n @if (isIgnored(fh.fileColIndex)) {\n <button class=\"btn btn-link btn-sm p-0 text-primary\" (click)=\"restoreMappingEntry(fh.fileColIndex)\" title=\"Include\">\n <i class=\"bi bi-plus-circle\"></i>\n </button>\n } @else {\n <button class=\"btn btn-link btn-sm p-0 text-secondary\" (click)=\"removeMappingEntry(fh.fileColIndex)\" title=\"Ignore this column\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('preview') {\n @let result = previewResult()!;\n\n @if (result.sampleCountMismatch) {\n <div class=\"alert alert-warning py-2 small mb-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n @for (w of result.warnings; track $index) { {{ w }} }\n </div>\n }\n\n @if (result.columnsMatched.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1\">\n <i class=\"bi bi-pencil me-1\"></i>Columns to update\n <span class=\"badge bg-primary ms-1\">{{ result.columnsMatched.length }}</span>\n </div>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>Column</th>\n <th>Current value</th>\n <th>New value</th>\n <th class=\"text-center\">Modifiers</th>\n <th class=\"text-center\">Rows</th>\n </tr>\n </thead>\n <tbody>\n @for (col of result.columnsMatched; track col.columnId) {\n <tr [class.table-warning]=\"col.currentValue !== col.newValue && col.newValue !== null\">\n <td class=\"text-nowrap\">{{ col.columnName }}</td>\n <td class=\"text-muted text-truncate\" style=\"max-width:140px\">{{ col.currentValue ?? '\u2014' }}</td>\n <td class=\"text-truncate\" style=\"max-width:140px\">{{ col.newValue ?? '\u2014' }}</td>\n <td class=\"text-center\">\n @if (col.newModifiers.length > 0) {\n <span class=\"badge bg-info\">{{ col.newModifiers.length }}</span>\n } @else { \u2014 }\n </td>\n <td class=\"text-center\">{{ col.rowsChanged }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n @if (result.columnsToAdd.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1 text-warning\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Columns that will be created\n <span class=\"badge bg-warning text-dark ms-1\">{{ result.columnsToAdd.length }}</span>\n </div>\n <ul class=\"list-unstyled small mb-0\">\n @for (col of result.columnsToAdd; track col.fileColIndex) {\n <li><i class=\"bi bi-plus me-1 text-warning\"></i>{{ col.fileHeader }}</li>\n }\n </ul>\n </div>\n }\n\n @if (result.columnsMatched.length === 0 && result.columnsToAdd.length === 0) {\n <div class=\"text-center text-muted py-3 small\">\n <i class=\"bi bi-inbox d-block fs-4 mb-1\"></i>No columns would be changed.\n </div>\n }\n }\n\n @case ('confirming') {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border spinner-border-sm text-primary\"></div>\n <small class=\"d-block mt-2 text-muted\">Applying override...</small>\n </div>\n }\n\n @case ('done') {\n @let result = commitResult()!;\n <div class=\"alert alert-success py-2 small\">\n <i class=\"bi bi-check-circle me-1\"></i>{{ result.message }}\n </div>\n <ul class=\"list-unstyled small mb-0\">\n <li><strong>{{ result.columnsUpdated }}</strong> columns updated</li>\n @if (result.columnsAdded > 0) {\n <li><strong>{{ result.columnsAdded }}</strong> columns created</li>\n }\n </ul>\n }\n\n }\n</div>\n\n<div class=\"modal-footer\">\n @switch (step()) {\n @case ('upload') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"dismiss()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!selectedFile() || isLoading()\"\n (click)=\"generateMapping()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Next: Map Columns\n </button>\n }\n\n @case ('map') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToUpload()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"userMapping().length === 0 || isLoading()\"\n (click)=\"preview()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Preview Changes\n </button>\n }\n\n @case ('preview') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToMap()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-warning btn-sm\"\n [disabled]=\"previewResult()!.columnsMatched.length === 0 && previewResult()!.columnsToAdd.length === 0\"\n (click)=\"confirm()\">\n Confirm Import\n </button>\n }\n\n @case ('done') {\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"close()\">Close</button>\n }\n }\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.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11537
11649
  }
11538
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnOverrideImportModal, decorators: [{
11650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: ColumnOverrideImportModal, decorators: [{
11539
11651
  type: Component,
11540
11652
  args: [{ selector: 'ccv-column-override-import-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-arrow-up me-2\"></i>Import / Override Columns\n @if (config) {\n <small class=\"text-muted ms-2 fw-normal\">{{ config.tableName }}</small>\n }\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (error()) {\n <div class=\"alert alert-danger py-2 small\">{{ error() }}</div>\n }\n\n @switch (step()) {\n\n @case ('upload') {\n <form [formGroup]=\"optionsForm\">\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">File</label>\n <input\n type=\"file\"\n class=\"form-control form-control-sm\"\n accept=\".tsv,.txt,.xlsx,.xls,.csv\"\n (change)=\"onFileChange($event)\">\n <div class=\"form-text\">SDRF (.tsv), Excel (.xlsx), or CSV</div>\n </div>\n\n <div class=\"mb-3\">\n <label class=\"form-label fw-medium\">Initial mapping algorithm</label>\n <select class=\"form-select form-select-sm\" formControlName=\"algorithm\">\n <option value=\"name\">By name \u2014 match headers to columns with the same name</option>\n <option value=\"position\">By position \u2014 match by column index order</option>\n <option value=\"none\">None \u2014 map everything manually</option>\n </select>\n <div class=\"form-text\">Duplicate names on either side are always left unmatched for manual resolution.</div>\n </div>\n\n <div class=\"mb-3 d-flex flex-column gap-2\">\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateValue\" formControlName=\"updateValue\">\n <label class=\"form-check-label\" for=\"updateValue\">Update default value</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"updateModifiers\" formControlName=\"updateModifiers\">\n <label class=\"form-check-label\" for=\"updateModifiers\">Update per-sample values</label>\n </div>\n <div class=\"form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"normalizeOntology\" formControlName=\"normalizeOntology\">\n <label class=\"form-check-label\" for=\"normalizeOntology\">Normalise ontology terms on commit</label>\n </div>\n </div>\n </form>\n }\n\n @case ('map') {\n @let result = suggestResult()!;\n\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <small class=\"text-muted\">\n {{ result.fileHeaders.length }} file columns &middot; {{ result.tableColumns.length }} table columns\n @if (newColumnCount() > 0) {\n &middot; <span class=\"text-warning fw-medium\">{{ newColumnCount() }} will be created</span>\n }\n </small>\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [formControl]=\"$any(optionsForm.get('algorithm'))\">\n <option value=\"name\">By name</option>\n <option value=\"position\">By position</option>\n <option value=\"none\">None</option>\n </select>\n <button class=\"btn btn-outline-secondary btn-sm\" [disabled]=\"isLoading()\" (click)=\"reapplyAlgorithm()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm\"></span>\n } @else {\n <i class=\"bi bi-arrow-clockwise\"></i>\n }\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>File column</th>\n <th>Maps to</th>\n <th class=\"text-center\" style=\"width:60px\">Ignore</th>\n </tr>\n </thead>\n <tbody>\n @for (fh of result.fileHeaders; track fh.fileColIndex) {\n <tr [class.table-warning]=\"!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null'\"\n [class.text-muted]=\"isIgnored(fh.fileColIndex)\">\n <td>\n {{ fh.header }}\n @if (!isIgnored(fh.fileColIndex) && getColumnIdForFileCol(fh.fileColIndex) === 'null') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"This column does not exist and will be created\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>new\n </span>\n }\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"badge bg-secondary ms-1\">ignored</span>\n }\n </td>\n <td>\n @if (isIgnored(fh.fileColIndex)) {\n <span class=\"text-muted fst-italic\">\u2014</span>\n } @else {\n <select\n class=\"form-select form-select-sm\"\n [value]=\"getColumnIdForFileCol(fh.fileColIndex)\"\n (change)=\"updateMappingEntry(fh.fileColIndex, $any($event.target).value)\">\n <option value=\"null\">Create new column</option>\n <optgroup label=\"Existing table columns\">\n @for (tc of result.tableColumns; track tc.columnId) {\n <option\n [value]=\"tc.columnId\"\n [disabled]=\"mappedColumnIds().has(tc.columnId) && getColumnIdForFileCol(fh.fileColIndex) !== tc.columnId.toString()\">\n {{ tc.columnName }}\n </option>\n }\n </optgroup>\n </select>\n }\n </td>\n <td class=\"text-center\">\n @if (isIgnored(fh.fileColIndex)) {\n <button class=\"btn btn-link btn-sm p-0 text-primary\" (click)=\"restoreMappingEntry(fh.fileColIndex)\" title=\"Include\">\n <i class=\"bi bi-plus-circle\"></i>\n </button>\n } @else {\n <button class=\"btn btn-link btn-sm p-0 text-secondary\" (click)=\"removeMappingEntry(fh.fileColIndex)\" title=\"Ignore this column\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('preview') {\n @let result = previewResult()!;\n\n @if (result.sampleCountMismatch) {\n <div class=\"alert alert-warning py-2 small mb-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n @for (w of result.warnings; track $index) { {{ w }} }\n </div>\n }\n\n @if (result.columnsMatched.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1\">\n <i class=\"bi bi-pencil me-1\"></i>Columns to update\n <span class=\"badge bg-primary ms-1\">{{ result.columnsMatched.length }}</span>\n </div>\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0 small\">\n <thead class=\"table-light\">\n <tr>\n <th>Column</th>\n <th>Current value</th>\n <th>New value</th>\n <th class=\"text-center\">Modifiers</th>\n <th class=\"text-center\">Rows</th>\n </tr>\n </thead>\n <tbody>\n @for (col of result.columnsMatched; track col.columnId) {\n <tr [class.table-warning]=\"col.currentValue !== col.newValue && col.newValue !== null\">\n <td class=\"text-nowrap\">{{ col.columnName }}</td>\n <td class=\"text-muted text-truncate\" style=\"max-width:140px\">{{ col.currentValue ?? '\u2014' }}</td>\n <td class=\"text-truncate\" style=\"max-width:140px\">{{ col.newValue ?? '\u2014' }}</td>\n <td class=\"text-center\">\n @if (col.newModifiers.length > 0) {\n <span class=\"badge bg-info\">{{ col.newModifiers.length }}</span>\n } @else { \u2014 }\n </td>\n <td class=\"text-center\">{{ col.rowsChanged }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n @if (result.columnsToAdd.length > 0) {\n <div class=\"mb-3\">\n <div class=\"fw-medium small mb-1 text-warning\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Columns that will be created\n <span class=\"badge bg-warning text-dark ms-1\">{{ result.columnsToAdd.length }}</span>\n </div>\n <ul class=\"list-unstyled small mb-0\">\n @for (col of result.columnsToAdd; track col.fileColIndex) {\n <li><i class=\"bi bi-plus me-1 text-warning\"></i>{{ col.fileHeader }}</li>\n }\n </ul>\n </div>\n }\n\n @if (result.columnsMatched.length === 0 && result.columnsToAdd.length === 0) {\n <div class=\"text-center text-muted py-3 small\">\n <i class=\"bi bi-inbox d-block fs-4 mb-1\"></i>No columns would be changed.\n </div>\n }\n }\n\n @case ('confirming') {\n <div class=\"text-center py-4\">\n <div class=\"spinner-border spinner-border-sm text-primary\"></div>\n <small class=\"d-block mt-2 text-muted\">Applying override...</small>\n </div>\n }\n\n @case ('done') {\n @let result = commitResult()!;\n <div class=\"alert alert-success py-2 small\">\n <i class=\"bi bi-check-circle me-1\"></i>{{ result.message }}\n </div>\n <ul class=\"list-unstyled small mb-0\">\n <li><strong>{{ result.columnsUpdated }}</strong> columns updated</li>\n @if (result.columnsAdded > 0) {\n <li><strong>{{ result.columnsAdded }}</strong> columns created</li>\n }\n </ul>\n }\n\n }\n</div>\n\n<div class=\"modal-footer\">\n @switch (step()) {\n @case ('upload') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"dismiss()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!selectedFile() || isLoading()\"\n (click)=\"generateMapping()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Next: Map Columns\n </button>\n }\n\n @case ('map') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToUpload()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"userMapping().length === 0 || isLoading()\"\n (click)=\"preview()\">\n @if (isLoading()) { <span class=\"spinner-border spinner-border-sm me-1\"></span> }\n Preview Changes\n </button>\n }\n\n @case ('preview') {\n <button type=\"button\" class=\"btn btn-secondary btn-sm\" (click)=\"backToMap()\">Back</button>\n <button\n type=\"button\"\n class=\"btn btn-warning btn-sm\"\n [disabled]=\"previewResult()!.columnsMatched.length === 0 && previewResult()!.columnsToAdd.length === 0\"\n (click)=\"confirm()\">\n Confirm Import\n </button>\n }\n\n @case ('done') {\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"close()\">Close</button>\n }\n }\n</div>\n" }]
11541
11653
  }], propDecorators: { config: [{
@@ -11543,14 +11655,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11543
11655
  }] } });
11544
11656
 
11545
11657
  class CupcakeVanilla {
11546
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
11547
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
11658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
11659
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
11548
11660
  <p>
11549
11661
  cupcake-vanilla works!
11550
11662
  </p>
11551
11663
  `, isInline: true, styles: [""], changeDetection: i0.ChangeDetectionStrategy.Eager });
11552
11664
  }
11553
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CupcakeVanilla, decorators: [{
11665
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: CupcakeVanilla, decorators: [{
11554
11666
  type: Component,
11555
11667
  args: [{ selector: 'ccv-cupcake-vanilla', imports: [], template: `
11556
11668
  <p>
@@ -11567,5 +11679,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11567
11679
  * Generated bundle index. Do not edit.
11568
11680
  */
11569
11681
 
11570
- export { AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnOverrideImportModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, DesktopService, DualScrollDirective, ElectronService, ExcelExportModalComponent, ExcelLaunchService, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnAutofillModal, MetadataColumnHistoryModal, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableDetails, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OfficialColumnCacheService, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, OpenInExcel, SDRFDefaultsService, SamplePoolCreateModal, SamplePoolDetailsModal, SamplePoolEditModal, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfNumberWithUnitInput, SdrfSelectInput, SdrfSpikedCompoundInput, SdrfSyntaxService, SearchReplaceModal, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, createSyntheticColumnFromTemplate, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
11682
+ export { AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnOverrideImportModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, DesktopService, DualScrollDirective, ElectronService, ExcelExportModalComponent, ExcelLaunchService, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnAutofillModal, MetadataColumnHistoryModal, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableDetails, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OfficialColumnCacheService, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, OpenInExcel, SDRFDefaultsService, SamplePoolCreateModal, SamplePoolDetailsModal, SamplePoolEditModal, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfMzRangeInput, SdrfNumberWithUnitInput, SdrfSelectInput, SdrfSpikedCompoundInput, SdrfSyntaxService, SearchReplaceModal, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, createSyntheticColumnFromTemplate, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
11571
11683
  //# sourceMappingURL=noatgnu-cupcake-vanilla.mjs.map