@noatgnu/cupcake-vanilla 1.4.1 → 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'
@@ -3018,10 +3018,10 @@ class SdrfSyntaxService {
3018
3018
  getSyntheticPeptideValues() {
3019
3019
  return [...this.SYNTHETIC_PEPTIDE_VALUES];
3020
3020
  }
3021
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3022
- 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' });
3023
3023
  }
3024
- 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: [{
3025
3025
  type: Injectable,
3026
3026
  args: [{
3027
3027
  providedIn: 'root'
@@ -3210,10 +3210,10 @@ class DesktopService {
3210
3210
  return this.wailsRuntime.Call.ByName('main.App.LogToFile', message);
3211
3211
  }
3212
3212
  }
3213
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DesktopService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3214
- 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' });
3215
3215
  }
3216
- 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: [{
3217
3217
  type: Injectable,
3218
3218
  args: [{
3219
3219
  providedIn: 'root'
@@ -3279,10 +3279,10 @@ class ElectronService {
3279
3279
  onWindowStateChange() {
3280
3280
  return this.desktop.onWindowStateChange();
3281
3281
  }
3282
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3283
- 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' });
3284
3284
  }
3285
- 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: [{
3286
3286
  type: Injectable,
3287
3287
  args: [{
3288
3288
  providedIn: 'root'
@@ -3297,10 +3297,10 @@ class ExcelLaunchService extends BaseApiService {
3297
3297
  const request = { code };
3298
3298
  return this.post(`${this.apiUrl}/excel-launch/claim/`, request);
3299
3299
  }
3300
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelLaunchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3301
- 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' });
3302
3302
  }
3303
- 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: [{
3304
3304
  type: Injectable,
3305
3305
  args: [{
3306
3306
  providedIn: 'root'
@@ -3404,10 +3404,10 @@ class SdrfAgeInput {
3404
3404
  }
3405
3405
  }
3406
3406
  }
3407
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3408
- 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 });
3409
3409
  }
3410
- 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: [{
3411
3411
  type: Component,
3412
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" }]
3413
3413
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3695,10 +3695,10 @@ class SdrfModificationInput {
3695
3695
  this.updateAvailableSpecifications(mockSuggestion);
3696
3696
  }
3697
3697
  }
3698
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3699
- 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 });
3700
3700
  }
3701
- 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: [{
3702
3702
  type: Component,
3703
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" }]
3704
3704
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3790,10 +3790,10 @@ class SdrfSpikedCompoundInput {
3790
3790
  get showPeptideFields() {
3791
3791
  return this.spikedForm.get('CT')?.value === 'peptide';
3792
3792
  }
3793
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3794
- 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 });
3795
3795
  }
3796
- 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: [{
3797
3797
  type: Component,
3798
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" }]
3799
3799
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -3927,10 +3927,10 @@ class SdrfCleavageInput {
3927
3927
  onSearchTypeChange(type) {
3928
3928
  this.searchType.set(type);
3929
3929
  }
3930
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3931
- 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 });
3932
3932
  }
3933
- 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: [{
3934
3934
  type: Component,
3935
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" }]
3936
3936
  }], ctorParameters: () => [], propDecorators: { value: [{
@@ -4013,10 +4013,10 @@ class SdrfNumberWithUnitInput {
4013
4013
  this.parseAndSetValue(exampleStr);
4014
4014
  this.updateValue();
4015
4015
  }
4016
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfNumberWithUnitInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
4017
- 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 });
4018
4018
  }
4019
- 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: [{
4020
4020
  type: Component,
4021
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" }]
4022
4022
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { value: [{
@@ -4090,10 +4090,10 @@ class SdrfSelectInput {
4090
4090
  this.selectedValue.set('__custom__');
4091
4091
  }
4092
4092
  }
4093
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfSelectInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4094
- 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 });
4095
4095
  }
4096
- 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: [{
4097
4097
  type: Component,
4098
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" }]
4099
4099
  }], propDecorators: { value: [{
@@ -4148,10 +4148,10 @@ class SdrfMzRangeInput {
4148
4148
  this.valueChange.emit('');
4149
4149
  }
4150
4150
  }
4151
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfMzRangeInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
4152
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", 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 });
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
4153
  }
4154
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfMzRangeInput, decorators: [{
4154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SdrfMzRangeInput, decorators: [{
4155
4155
  type: Component,
4156
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
4157
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { value: [{
@@ -4680,10 +4680,10 @@ class MetadataValueEditModal {
4680
4680
  }
4681
4681
  }
4682
4682
  }
4683
- 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 });
4684
- 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 || 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 });
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 });
4685
4685
  }
4686
- 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: [{
4687
4687
  type: Component,
4688
4688
  args: [{ selector: 'ccv-metadata-value-edit-modal', standalone: true, imports: [
4689
4689
  CommonModule,
@@ -4783,10 +4783,10 @@ class ExcelExportModalComponent {
4783
4783
  cancel() {
4784
4784
  this.activeModal.dismiss();
4785
4785
  }
4786
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4787
- 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 });
4788
4788
  }
4789
- 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: [{
4790
4790
  type: Component,
4791
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" }]
4792
4792
  }] });
@@ -4803,10 +4803,10 @@ class ValidationResultsModal {
4803
4803
  return timestamp;
4804
4804
  }
4805
4805
  }
4806
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4807
- 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 });
4808
4808
  }
4809
- 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: [{
4810
4810
  type: Component,
4811
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"] }]
4812
4812
  }], propDecorators: { results: [{
@@ -5215,10 +5215,10 @@ class AsyncTaskUIService {
5215
5215
  break;
5216
5216
  }
5217
5217
  }
5218
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5219
- 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' });
5220
5220
  }
5221
- 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: [{
5222
5222
  type: Injectable,
5223
5223
  args: [{
5224
5224
  providedIn: 'root'
@@ -5327,10 +5327,10 @@ class MetadataValidationModal {
5327
5327
  }
5328
5328
  });
5329
5329
  }
5330
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
5331
- 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 });
5332
5332
  }
5333
- 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: [{
5334
5334
  type: Component,
5335
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"] }]
5336
5336
  }], propDecorators: { config: [{
@@ -5548,10 +5548,10 @@ class MetadataTableEditModal {
5548
5548
  };
5549
5549
  return displayNames[fieldName] || fieldName;
5550
5550
  }
5551
- 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 });
5552
- 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 });
5553
5553
  }
5554
- 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: [{
5555
5555
  type: Component,
5556
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" }]
5557
5557
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableService }, { type: i3.LabGroupService }, { type: i3.ConfirmDialogService }], propDecorators: { table: [{
@@ -6022,10 +6022,10 @@ class ColumnEditModal {
6022
6022
  customFilters
6023
6023
  };
6024
6024
  }
6025
- 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 });
6026
- 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 });
6027
6027
  }
6028
- 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: [{
6029
6029
  type: Component,
6030
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"] }]
6031
6031
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataColumnTemplateService }, { type: SchemaService }, { type: SdrfSyntaxService }, { type: OfficialColumnCacheService }], propDecorators: { column: [{
@@ -6464,10 +6464,10 @@ class MetadataTableTemplateEditModal {
6464
6464
  columns.length = 0;
6465
6465
  columns.push(...regroupedColumns);
6466
6466
  }
6467
- 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 });
6468
- 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 });
6469
6469
  }
6470
- 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: [{
6471
6471
  type: Component,
6472
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"] }]
6473
6473
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: MetadataColumnService }, { type: i3.ToastService }, { type: i3.ConfirmDialogService }], propDecorators: { template: [{
@@ -6669,10 +6669,10 @@ class TemplateSchemaSelectionModal {
6669
6669
  onCancel() {
6670
6670
  this.activeModal.dismiss();
6671
6671
  }
6672
- 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 });
6673
- 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 });
6674
6674
  }
6675
- 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: [{
6676
6676
  type: Component,
6677
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"] }]
6678
6678
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { availableSchemas: [{
@@ -6782,10 +6782,10 @@ class SchemaSelectionModal {
6782
6782
  };
6783
6783
  return categoryMap[category.toLowerCase()] || 'bg-secondary';
6784
6784
  }
6785
- 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 });
6786
- 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 });
6787
6787
  }
6788
- 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: [{
6789
6789
  type: Component,
6790
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"] }]
6791
6791
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }, { type: SchemaService }, { type: i3.ToastService }] });
@@ -6872,10 +6872,10 @@ class TableCreationModalComponent {
6872
6872
  };
6873
6873
  return labels[fieldName] || fieldName;
6874
6874
  }
6875
- 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 });
6876
- 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 });
6877
6877
  }
6878
- 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: [{
6879
6879
  type: Component,
6880
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"] }]
6881
6881
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { template: [{
@@ -7354,10 +7354,10 @@ class MetadataTableTemplates {
7354
7354
  }
7355
7355
  });
7356
7356
  }
7357
- 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 });
7358
- 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 });
7359
7359
  }
7360
- 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: [{
7361
7361
  type: Component,
7362
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" }]
7363
7363
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataTableTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }, { type: i3.ToastService }, { type: AsyncTaskUIService }, { type: i3.ConfirmDialogService }] });
@@ -7678,10 +7678,10 @@ class ColumnTemplateEditModal {
7678
7678
  this.editForm.get('defaultValue')?.markAsTouched();
7679
7679
  }
7680
7680
  };
7681
- 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 });
7682
- 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 });
7683
7683
  }
7684
- 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: [{
7685
7685
  type: Component,
7686
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" }]
7687
7687
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: MetadataColumnTemplateService }, { type: OntologySearchService }], propDecorators: { template: [{
@@ -8294,10 +8294,10 @@ class ColumnTemplates {
8294
8294
  }
8295
8295
  this.expandedGroups.set(expanded);
8296
8296
  }
8297
- 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 });
8298
- 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 });
8299
8299
  }
8300
- 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: [{
8301
8301
  type: Component,
8302
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"] }]
8303
8303
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: SchemaService }, { type: i2.NgbModal }, { type: i3.ConfirmDialogService }] });
@@ -9038,10 +9038,10 @@ class FavoriteManagement {
9038
9038
  return 'bi-star';
9039
9039
  return 'bi-circle';
9040
9040
  }
9041
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FavoriteManagement, deps: [], target: i0.ɵɵFactoryTarget.Component });
9042
- 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 });
9043
9043
  }
9044
- 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: [{
9045
9045
  type: Component,
9046
9046
  args: [{ selector: 'ccv-favorite-management', standalone: true, imports: [
9047
9047
  CommonModule,
@@ -9110,10 +9110,10 @@ class SearchReplaceModal {
9110
9110
  }
9111
9111
  return '';
9112
9112
  }
9113
- 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 });
9114
- 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 });
9115
9115
  }
9116
- 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: [{
9117
9117
  type: Component,
9118
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" }]
9119
9119
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -9155,10 +9155,10 @@ class SamplePoolDetailsModal {
9155
9155
  const pooledAndIndependent = this.pool?.pooledAndIndependentSamples?.length || 0;
9156
9156
  return pooledOnly + pooledAndIndependent;
9157
9157
  }
9158
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9159
- 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 });
9160
9160
  }
9161
- 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: [{
9162
9162
  type: Component,
9163
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>" }]
9164
9164
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { pool: [{
@@ -9311,10 +9311,10 @@ class SamplePoolEditModal {
9311
9311
  get totalSamplesCount() {
9312
9312
  return this.pooledOnlySamplesCount + this.pooledAndIndependentSamplesCount;
9313
9313
  }
9314
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
9315
- 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 });
9316
9316
  }
9317
- 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: [{
9318
9318
  type: Component,
9319
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>" }]
9320
9320
  }], ctorParameters: () => [], propDecorators: { pool: [{
@@ -9665,10 +9665,10 @@ class SamplePoolCreateModal {
9665
9665
  }
9666
9666
  return false;
9667
9667
  }
9668
- 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 });
9669
- 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 });
9670
9670
  }
9671
- 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: [{
9672
9672
  type: Component,
9673
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" }]
9674
9674
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
@@ -9721,10 +9721,10 @@ class DualScrollDirective {
9721
9721
  this.el.nativeElement.removeEventListener('scroll', this.onHostScroll);
9722
9722
  this.topBar?.remove();
9723
9723
  }
9724
- 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 });
9725
- 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 });
9726
9726
  }
9727
- 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: [{
9728
9728
  type: Directive,
9729
9729
  args: [{
9730
9730
  selector: '[appDualScroll]',
@@ -9901,7 +9901,7 @@ class MetadataTableDetails {
9901
9901
  ngOnInit() {
9902
9902
  this.route.params.subscribe(params => {
9903
9903
  const id = Number(params['id']);
9904
- const mode = params['mode'] || 'list';
9904
+ const mode = params['mode'] || 'table';
9905
9905
  if (id && !isNaN(id)) {
9906
9906
  this.tableId.set(id);
9907
9907
  this.viewMode.set(mode);
@@ -9921,7 +9921,6 @@ class MetadataTableDetails {
9921
9921
  setupAsyncTaskRefreshListener(tableId) {
9922
9922
  this.asyncTaskService.metadataTableRefresh$.pipe(takeUntil$1(this.destroy$)).subscribe((refreshedTableId) => {
9923
9923
  if (refreshedTableId === tableId) {
9924
- console.log(`Import task completed for table ${refreshedTableId}, refreshing table details`);
9925
9924
  this.toastService.info('Refreshing table data after successful import');
9926
9925
  this.loadTable(tableId);
9927
9926
  }
@@ -9938,7 +9937,6 @@ class MetadataTableDetails {
9938
9937
  },
9939
9938
  error: (error) => {
9940
9939
  this.isLoading.set(false);
9941
- console.error('Error loading metadata table:', error);
9942
9940
  const errorMsg = error?.error?.detail || 'Table not found';
9943
9941
  this.toastService.error(errorMsg);
9944
9942
  if (error.status === 404) {
@@ -9988,11 +9986,9 @@ class MetadataTableDetails {
9988
9986
  });
9989
9987
  }
9990
9988
  triggerFileInput() {
9991
- console.log('triggerFileInput called');
9992
9989
  this.fileInput.nativeElement.click();
9993
9990
  }
9994
9991
  triggerExcelInput() {
9995
- console.log('triggerExcelInput called');
9996
9992
  this.excelInput.nativeElement.click();
9997
9993
  }
9998
9994
  async importSdrf(event) {
@@ -10133,7 +10129,6 @@ class MetadataTableDetails {
10133
10129
  this.toastService.success(`Excel export queued successfully! Task ID: ${response.taskId}`);
10134
10130
  },
10135
10131
  error: (error) => {
10136
- console.error('Error queuing Excel export:', error);
10137
10132
  const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue Excel export';
10138
10133
  this.toastService.error(errorMsg);
10139
10134
  }
@@ -10152,7 +10147,6 @@ class MetadataTableDetails {
10152
10147
  this.toastService.success(`SDRF export queued successfully! Task ID: ${response.taskId}`);
10153
10148
  },
10154
10149
  error: (error) => {
10155
- console.error('Error queuing SDRF export:', error);
10156
10150
  const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue SDRF export';
10157
10151
  this.toastService.error(errorMsg);
10158
10152
  }
@@ -10167,7 +10161,6 @@ class MetadataTableDetails {
10167
10161
  },
10168
10162
  error: (error) => {
10169
10163
  this.isLoading.set(false);
10170
- console.error('Error exporting table:', error);
10171
10164
  const errorMsg = error?.error?.detail || 'Failed to export table';
10172
10165
  this.toastService.error(errorMsg);
10173
10166
  }
@@ -10186,7 +10179,6 @@ class MetadataTableDetails {
10186
10179
  },
10187
10180
  error: (error) => {
10188
10181
  this.isLoading.set(false);
10189
- console.error('Error deleting metadata table:', error);
10190
10182
  const errorMsg = error?.error?.detail || error?.message || 'Failed to delete table. Please try again.';
10191
10183
  this.toastService.error(errorMsg);
10192
10184
  }
@@ -10481,7 +10473,6 @@ class MetadataTableDetails {
10481
10473
  modalRef.componentInstance.onClose();
10482
10474
  },
10483
10475
  error: (error) => {
10484
- console.error('Error updating column value:', error);
10485
10476
  this.toastService.error('Failed to update column value');
10486
10477
  }
10487
10478
  });
@@ -10506,7 +10497,6 @@ class MetadataTableDetails {
10506
10497
  this.toastService.success(`Column "${column.name}" visibility updated successfully!`);
10507
10498
  },
10508
10499
  error: (error) => {
10509
- console.error('Error updating column hidden property:', error);
10510
10500
  this.toastService.error('Failed to update column visibility');
10511
10501
  }
10512
10502
  });
@@ -10540,13 +10530,9 @@ class MetadataTableDetails {
10540
10530
  }
10541
10531
  else {
10542
10532
  this.toastService.warning(`Auto-filled ${response.updatedCount} sample(s), ${response.failedCount} failed for "${column.name}"`);
10543
- if (response.failedUpdates) {
10544
- console.error('Failed updates:', response.failedUpdates);
10545
- }
10546
10533
  }
10547
10534
  },
10548
10535
  error: (error) => {
10549
- console.error('Error auto-filling column values:', error);
10550
10536
  this.toastService.error('Failed to auto-fill column values');
10551
10537
  }
10552
10538
  });
@@ -10641,7 +10627,6 @@ class MetadataTableDetails {
10641
10627
  this.loadTable(currentTable.id);
10642
10628
  },
10643
10629
  error: (error) => {
10644
- console.error('Error updating column settings:', error);
10645
10630
  this.toastService.error('Failed to update column settings');
10646
10631
  }
10647
10632
  });
@@ -10734,7 +10719,6 @@ class MetadataTableDetails {
10734
10719
  modalRef.componentInstance.onClose();
10735
10720
  },
10736
10721
  error: (error) => {
10737
- console.error('Error updating sample value:', error);
10738
10722
  this.toastService.error(`Failed to update sample ${sampleIndex} value`);
10739
10723
  }
10740
10724
  });
@@ -10837,7 +10821,6 @@ class MetadataTableDetails {
10837
10821
  this.loadTable(currentTable.id);
10838
10822
  },
10839
10823
  error: (error) => {
10840
- console.error('Error adding column:', error);
10841
10824
  this.toastService.error('Failed to add column');
10842
10825
  },
10843
10826
  complete: () => {
@@ -10875,14 +10858,12 @@ class MetadataTableDetails {
10875
10858
  this.isLoading.set(true);
10876
10859
  this.metadataTableService.reorderColumnsBySchemaAsync(tableId, schemaIds).subscribe({
10877
10860
  next: (response) => {
10878
- console.log('Column reorder task started:', response);
10879
10861
  this.toastService.success(`Column reorder task queued successfully! Task ID: ${response.taskId}`);
10880
10862
  // Mark task for monitoring and start real-time updates (same pattern as imports)
10881
10863
  this.asyncTaskService.monitorTask(response.taskId);
10882
10864
  this.isLoading.set(false);
10883
10865
  },
10884
10866
  error: (error) => {
10885
- console.error('Error starting column reorder:', error);
10886
10867
  this.toastService.error('Failed to start column reordering');
10887
10868
  this.isLoading.set(false);
10888
10869
  }
@@ -10912,7 +10893,6 @@ class MetadataTableDetails {
10912
10893
  this.table.update(table => table ? { ...table, columns: updatedTableColumns } : null);
10913
10894
  const draggedColumn = dragColumns[event.currentIndex];
10914
10895
  const newPosition = event.currentIndex;
10915
- console.log(`Moving column "${draggedColumn.name}" to position ${newPosition}`);
10916
10896
  this.metadataTableService.reorderColumn(currentTable.id, draggedColumn.id, newPosition).subscribe({
10917
10897
  next: () => {
10918
10898
  this.toastService.success('Column reordered successfully');
@@ -10924,10 +10904,10 @@ class MetadataTableDetails {
10924
10904
  }
10925
10905
  });
10926
10906
  }
10927
- 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 });
10928
- 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 });
10929
10909
  }
10930
- 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: [{
10931
10911
  type: Component,
10932
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"] }]
10933
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: [{
@@ -11116,10 +11096,10 @@ class BasicAutofillComponent {
11116
11096
  return !!(field && field.invalid && field.touched);
11117
11097
  }
11118
11098
  Math = Math;
11119
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BasicAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
11120
- 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 });
11121
11101
  }
11122
- 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: [{
11123
11103
  type: Component,
11124
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" }]
11125
11105
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -11270,10 +11250,10 @@ class AdvancedAutofillComponent {
11270
11250
  };
11271
11251
  this.submit.emit(request);
11272
11252
  }
11273
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AdvancedAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
11274
- 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 });
11275
11255
  }
11276
- 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: [{
11277
11257
  type: Component,
11278
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" }]
11279
11259
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -11322,10 +11302,10 @@ class MetadataColumnAutofillModal {
11322
11302
  close() {
11323
11303
  this.activeModal.dismiss('cancel');
11324
11304
  }
11325
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
11326
- 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 });
11327
11307
  }
11328
- 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: [{
11329
11309
  type: Component,
11330
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"] }]
11331
11311
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -11418,10 +11398,10 @@ class MetadataColumnHistoryModal {
11418
11398
  default: return 'badge bg-secondary';
11419
11399
  }
11420
11400
  }
11421
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataColumnHistoryModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11422
- 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 });
11423
11403
  }
11424
- 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: [{
11425
11405
  type: Component,
11426
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"] }]
11427
11407
  }], propDecorators: { config: [{
@@ -11490,10 +11470,10 @@ class OpenInExcel {
11490
11470
  const diffMins = Math.ceil(code.expiresIn / 60);
11491
11471
  return `${diffMins} minute${diffMins !== 1 ? 's' : ''}`;
11492
11472
  }
11493
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: OpenInExcel, deps: [], target: i0.ɵɵFactoryTarget.Component });
11494
- 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 });
11495
11475
  }
11496
- 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: [{
11497
11477
  type: Component,
11498
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"] }]
11499
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"] }] } });
@@ -11664,10 +11644,10 @@ class ColumnOverrideImportModal {
11664
11644
  normalizeOntology: v.normalizeOntology,
11665
11645
  };
11666
11646
  }
11667
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ColumnOverrideImportModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
11668
- 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 });
11669
11649
  }
11670
- 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: [{
11671
11651
  type: Component,
11672
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" }]
11673
11653
  }], propDecorators: { config: [{
@@ -11675,14 +11655,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11675
11655
  }] } });
11676
11656
 
11677
11657
  class CupcakeVanilla {
11678
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
11679
- 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: `
11680
11660
  <p>
11681
11661
  cupcake-vanilla works!
11682
11662
  </p>
11683
11663
  `, isInline: true, styles: [""], changeDetection: i0.ChangeDetectionStrategy.Eager });
11684
11664
  }
11685
- 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: [{
11686
11666
  type: Component,
11687
11667
  args: [{ selector: 'ccv-cupcake-vanilla', imports: [], template: `
11688
11668
  <p>