@noatgnu/cupcake-vanilla 1.2.15 → 1.2.17

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.
@@ -5571,11 +5571,11 @@ class MetadataTableTemplateEditModal {
5571
5571
  columns.push(...regroupedColumns);
5572
5572
  }
5573
5573
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5574
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableTemplateEditModal, isStandalone: true, selector: "ccv-metadata-table-template-edit-modal", inputs: { template: "template", labGroups: "labGroups", isEdit: "isEdit" }, outputs: { templateSaved: "templateSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
5574
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableTemplateEditModal, isStandalone: true, selector: "ccv-metadata-table-template-edit-modal", inputs: { template: "template", labGroups: "labGroups", isEdit: "isEdit" }, outputs: { templateSaved: "templateSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
5575
5575
  }
5576
5576
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
5577
5577
  type: Component,
5578
- args: [{ selector: 'ccv-metadata-table-template-edit-modal', imports: [CommonModule, ReactiveFormsModule, NgbModule, DragDropModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"] }]
5578
+ args: [{ selector: 'ccv-metadata-table-template-edit-modal', imports: [CommonModule, ReactiveFormsModule, NgbModule, DragDropModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"] }]
5579
5579
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: i3.ToastService }], propDecorators: { template: [{
5580
5580
  type: Input
5581
5581
  }], labGroups: [{
@@ -7623,11 +7623,11 @@ class SearchReplaceModal {
7623
7623
  return '';
7624
7624
  }
7625
7625
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SearchReplaceModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7626
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
7626
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
7627
7627
  }
7628
7628
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SearchReplaceModal, decorators: [{
7629
7629
  type: Component,
7630
- args: [{ selector: 'ccv-search-replace-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n" }]
7630
+ args: [{ selector: 'ccv-search-replace-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n" }]
7631
7631
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
7632
7632
  type: Input
7633
7633
  }] } });
@@ -7668,11 +7668,11 @@ class SamplePoolDetailsModal {
7668
7668
  return pooledOnly + pooledAndIndependent;
7669
7669
  }
7670
7670
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7671
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7671
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7672
7672
  }
7673
7673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
7674
7674
  type: Component,
7675
- args: [{ selector: 'ccv-sample-pool-details-modal', standalone: true, imports: [CommonModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>" }]
7675
+ args: [{ selector: 'ccv-sample-pool-details-modal', standalone: true, imports: [CommonModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>" }]
7676
7676
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { pool: [{
7677
7677
  type: Input
7678
7678
  }] } });
@@ -7852,7 +7852,16 @@ class SamplePoolCreateModal {
7852
7852
  // UI state
7853
7853
  selectionMode = signal('manual', ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
7854
7854
  currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
7855
- samplesPerPage = 20;
7855
+ samplesPerPage = 10;
7856
+ sourceNameColumn = computed(() => {
7857
+ return this.metadataTable?.columns?.find(c => c.name === 'source name');
7858
+ }, ...(ngDevMode ? [{ debugName: "sourceNameColumn" }] : []));
7859
+ assayNameColumn = computed(() => {
7860
+ return this.metadataTable?.columns?.find(c => c.name === 'assay name');
7861
+ }, ...(ngDevMode ? [{ debugName: "assayNameColumn" }] : []));
7862
+ fractionIdentifierColumn = computed(() => {
7863
+ return this.metadataTable?.columns?.find(c => c.name === 'comment[fraction identifier]');
7864
+ }, ...(ngDevMode ? [{ debugName: "fractionIdentifierColumn" }] : []));
7856
7865
  constructor(fb, activeModal, samplePoolService, toastService) {
7857
7866
  this.fb = fb;
7858
7867
  this.activeModal = activeModal;
@@ -7949,6 +7958,28 @@ class SamplePoolCreateModal {
7949
7958
  this.samples.set([...samples]);
7950
7959
  this.updateSampleTextInputs();
7951
7960
  }
7961
+ selectAllNonPooledSamples() {
7962
+ const samples = this.samples();
7963
+ samples.forEach(s => {
7964
+ if (!s.pooled) {
7965
+ s.selected = true;
7966
+ }
7967
+ });
7968
+ this.samples.set([...samples]);
7969
+ this.updateSampleTextInputs();
7970
+ }
7971
+ toggleSelectAllSamples() {
7972
+ const nonPooledCount = this.samples().filter(s => !s.pooled).length;
7973
+ if (this.selectedSamplesCount === nonPooledCount) {
7974
+ this.clearAllSelections();
7975
+ }
7976
+ else {
7977
+ this.selectAllNonPooledSamples();
7978
+ }
7979
+ }
7980
+ get availableSamplesCount() {
7981
+ return this.samples().filter(s => !s.pooled).length;
7982
+ }
7952
7983
  updateSampleTextInputs() {
7953
7984
  const selectedSamples = this.samples().filter(s => s.selected).map(s => s.sampleNumber);
7954
7985
  const pooledOnly = this.pooledOnlySamples();
@@ -8100,12 +8131,40 @@ class SamplePoolCreateModal {
8100
8131
  this.currentPage.set(this.currentPage() + 1);
8101
8132
  }
8102
8133
  }
8134
+ getColumnValueForSample(column, sampleIndex) {
8135
+ if (!column)
8136
+ return '';
8137
+ const modifier = column.modifiers?.find(m => this.isSampleInRange(sampleIndex, m.samples));
8138
+ if (modifier) {
8139
+ return modifier.value;
8140
+ }
8141
+ return column.value || '';
8142
+ }
8143
+ isSampleInRange(sampleIndex, rangeStr) {
8144
+ if (!rangeStr)
8145
+ return false;
8146
+ const ranges = rangeStr.split(',').map(s => s.trim());
8147
+ for (const range of ranges) {
8148
+ if (range.includes('-')) {
8149
+ const [start, end] = range.split('-').map(n => parseInt(n.trim(), 10));
8150
+ if (sampleIndex >= start && sampleIndex <= end) {
8151
+ return true;
8152
+ }
8153
+ }
8154
+ else {
8155
+ if (sampleIndex === parseInt(range, 10)) {
8156
+ return true;
8157
+ }
8158
+ }
8159
+ }
8160
+ return false;
8161
+ }
8103
8162
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
8104
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }] });
8163
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>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(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
8105
8164
  }
8106
8165
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
8107
8166
  type: Component,
8108
- args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n" }]
8167
+ args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>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(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n" }]
8109
8168
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
8110
8169
  type: Input
8111
8170
  }], poolCreated: [{
@@ -8665,6 +8724,9 @@ class MetadataTableDetails {
8665
8724
  return baseClass;
8666
8725
  }
8667
8726
  getShortColumnName(column) {
8727
+ if (column.displayName) {
8728
+ return column.displayName;
8729
+ }
8668
8730
  let name = column.name;
8669
8731
  // Handle different column types
8670
8732
  const type = column.type?.toLowerCase() || '';
@@ -8922,10 +8984,11 @@ class MetadataTableDetails {
8922
8984
  column: column,
8923
8985
  metadataTableId: table.id,
8924
8986
  sampleCount: table.sampleCount,
8925
- selectedSampleIndices: []
8987
+ selectedSampleIndices: [],
8988
+ allColumns: table.columns || []
8926
8989
  };
8927
8990
  const modalRef = this.modalService.open(MetadataColumnAutofillModal, {
8928
- size: 'lg',
8991
+ size: 'xl',
8929
8992
  backdrop: 'static'
8930
8993
  });
8931
8994
  modalRef.componentInstance.config = config;
@@ -9310,9 +9373,25 @@ class MetadataColumnAutofillModal {
9310
9373
  isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : []));
9311
9374
  fillMode = signal('template', ...(ngDevMode ? [{ debugName: "fillMode" }] : []));
9312
9375
  sampleIndices = signal([], ...(ngDevMode ? [{ debugName: "sampleIndices" }] : []));
9376
+ currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
9377
+ pageSize = 10;
9313
9378
  allSampleNumbers = computed(() => {
9314
9379
  return Array.from({ length: this.config.sampleCount }, (_, idx) => idx + 1);
9315
9380
  }, ...(ngDevMode ? [{ debugName: "allSampleNumbers" }] : []));
9381
+ paginatedSampleNumbers = computed(() => {
9382
+ const all = this.allSampleNumbers();
9383
+ const start = (this.currentPage() - 1) * this.pageSize;
9384
+ return all.slice(start, start + this.pageSize);
9385
+ }, ...(ngDevMode ? [{ debugName: "paginatedSampleNumbers" }] : []));
9386
+ sourceNameColumn = computed(() => {
9387
+ return this.config.allColumns?.find(c => c.name === 'source name');
9388
+ }, ...(ngDevMode ? [{ debugName: "sourceNameColumn" }] : []));
9389
+ assayNameColumn = computed(() => {
9390
+ return this.config.allColumns?.find(c => c.name === 'assay name');
9391
+ }, ...(ngDevMode ? [{ debugName: "assayNameColumn" }] : []));
9392
+ fractionIdentifierColumn = computed(() => {
9393
+ return this.config.allColumns?.find(c => c.name === 'comment[fraction identifier]');
9394
+ }, ...(ngDevMode ? [{ debugName: "fractionIdentifierColumn" }] : []));
9316
9395
  previewValues = computed(() => {
9317
9396
  const mode = this.fillMode();
9318
9397
  const indices = this.sampleIndices();
@@ -9393,6 +9472,37 @@ class MetadataColumnAutofillModal {
9393
9472
  isSampleSelected(index) {
9394
9473
  return this.sampleIndices().includes(index);
9395
9474
  }
9475
+ onPageChange(page) {
9476
+ this.currentPage.set(page);
9477
+ }
9478
+ getColumnValueForSample(column, sampleIndex) {
9479
+ if (!column)
9480
+ return '';
9481
+ const modifier = column.modifiers?.find(m => this.isSampleInRange(sampleIndex, m.samples));
9482
+ if (modifier) {
9483
+ return modifier.value;
9484
+ }
9485
+ return column.value || '';
9486
+ }
9487
+ isSampleInRange(sampleIndex, rangeStr) {
9488
+ if (!rangeStr)
9489
+ return false;
9490
+ const ranges = rangeStr.split(',').map(s => s.trim());
9491
+ for (const range of ranges) {
9492
+ if (range.includes('-')) {
9493
+ const [start, end] = range.split('-').map(n => parseInt(n.trim(), 10));
9494
+ if (sampleIndex >= start && sampleIndex <= end) {
9495
+ return true;
9496
+ }
9497
+ }
9498
+ else {
9499
+ if (sampleIndex === parseInt(range, 10)) {
9500
+ return true;
9501
+ }
9502
+ }
9503
+ }
9504
+ return false;
9505
+ }
9396
9506
  onSubmit() {
9397
9507
  if (this.autofillForm.invalid || this.isSubmitting()) {
9398
9508
  return;
@@ -9437,11 +9547,11 @@ class MetadataColumnAutofillModal {
9437
9547
  Array = Array;
9438
9548
  Math = Math;
9439
9549
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9440
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <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.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 >\n <i class=\"bi bi-check-all me-1\"></i>Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n >\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n </div>\n\n <div class=\"sample-grid\">\n @for (i of allSampleNumbers(); track i) {\n <button\n type=\"button\"\n class=\"btn btn-sm sample-button\"\n [class.btn-primary]=\"isSampleSelected(i)\"\n [class.btn-outline-secondary]=\"!isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n >\n {{ i }}\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Click sample numbers to select/deselect. 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</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\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]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".sample-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(45px,1fr));gap:.5rem;max-height:280px;overflow-y:auto;padding:.75rem;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-color:var(--bs-light)}.sample-button{min-width:45px;aspect-ratio:1;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.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-light)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:#fff;border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color-translucent)}.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: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
9550
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <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>Source Name</th>\n <th>Assay Name</th>\n <th>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(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</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\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]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\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: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbPaginationModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
9441
9551
  }
9442
9552
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
9443
9553
  type: Component,
9444
- args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], 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 <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.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 >\n <i class=\"bi bi-check-all me-1\"></i>Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n >\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n </div>\n\n <div class=\"sample-grid\">\n @for (i of allSampleNumbers(); track i) {\n <button\n type=\"button\"\n class=\"btn btn-sm sample-button\"\n [class.btn-primary]=\"isSampleSelected(i)\"\n [class.btn-outline-secondary]=\"!isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n >\n {{ i }}\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Click sample numbers to select/deselect. 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</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\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]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".sample-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(45px,1fr));gap:.5rem;max-height:280px;overflow-y:auto;padding:.75rem;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-color:var(--bs-light)}.sample-button{min-width:45px;aspect-ratio:1;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.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-light)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:#fff;border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color-translucent)}.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"] }]
9554
+ args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbPaginationModule], 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 <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>Source Name</th>\n <th>Assay Name</th>\n <th>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(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</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\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]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\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"] }]
9445
9555
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
9446
9556
  type: Input
9447
9557
  }] } });