diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 35e2a53a33..ec1d1ff1c8 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.16.2", + "version": "7.16.3-jobActionsUiUpdate.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.16.2", + "version": "7.16.3-jobActionsUiUpdate.2", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index d24ae64c35..f613fc6830 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.16.2", + "version": "7.16.3-jobActionsUiUpdate.2", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index e0a0aeb5fb..e1f9634d43 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,10 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version TBD +*Released*: TBD +- Update `FilterStatus` to optionally include an "Add Filter" button + ### version 7.16.2 *Released*: 11 February 2026 - GitHub Issue 779: Cannot Edit Relative Dates in Sample Finder diff --git a/packages/components/src/internal/url/AppURL.ts b/packages/components/src/internal/url/AppURL.ts index bf643529d5..6c65cb14eb 100644 --- a/packages/components/src/internal/url/AppURL.ts +++ b/packages/components/src/internal/url/AppURL.ts @@ -99,17 +99,19 @@ export class AppURL { } } - const stringPart = parts[i].toString(); - const newPart = encodeURIComponent(stringPart); - - if (i === 0) { - if (stringPart.indexOf('/') === 0) { - basePath += newPart; + if (parts[i]) { + const stringPart = parts[i].toString(); + const newPart = encodeURIComponent(stringPart); + + if (i === 0) { + if (stringPart.indexOf('/') === 0) { + basePath += newPart; + } else { + basePath += '/' + newPart; + } } else { basePath += '/' + newPart; } - } else { - basePath += '/' + newPart; } } diff --git a/packages/components/src/public/QueryModel/FilterStatus.test.tsx b/packages/components/src/public/QueryModel/FilterStatus.test.tsx index 60ad2ff1ee..cb8bcbe704 100644 --- a/packages/components/src/public/QueryModel/FilterStatus.test.tsx +++ b/packages/components/src/public/QueryModel/FilterStatus.test.tsx @@ -52,6 +52,7 @@ describe('FilterStatus', () => { }; const sortAction = { action: new SortAction(), + value: 'sort', }; function validate(valueCount: number, filterCount: number): void { @@ -132,4 +133,32 @@ describe('FilterStatus', () => { expect(document.querySelectorAll('.fa-close')).toHaveLength(0); expect(document.querySelectorAll('.remove-all-filters')).toHaveLength(0); }); + + test('with add filter', async () => { + render( + + ); + expect(document.querySelectorAll('.fa-table')).toHaveLength(0); + expect(document.querySelectorAll('.fa-search')).toHaveLength(0); + expect(document.querySelectorAll('.fa-filter')).toHaveLength(2); + expect(document.querySelectorAll('.remove-all-filters')).toHaveLength(0); + }); + + test('without actionValues, with add', async () => { + render( + + ); + expect(document.querySelectorAll('.filter-status-value')).toHaveLength(0); + expect(document.querySelectorAll('.fa-filter')).toHaveLength(1); + }); }); diff --git a/packages/components/src/public/QueryModel/FilterStatus.tsx b/packages/components/src/public/QueryModel/FilterStatus.tsx index b4cec32b46..f855a1efa4 100644 --- a/packages/components/src/public/QueryModel/FilterStatus.tsx +++ b/packages/components/src/public/QueryModel/FilterStatus.tsx @@ -3,58 +3,75 @@ import React, { FC, memo } from 'react'; import { ActionValue } from './grid/actions/Action'; import { Value } from './grid/Value'; import { filterActionValuesByType } from './grid/utils'; +import classNames from 'classnames'; interface Props { actionValues: ActionValue[]; lockReadOnlyForDelete?: boolean; + onAddFilterClick?: () => void; onClick: (actionValue: ActionValue, event: any) => void; onRemove: (actionValueIndex: number, event: any) => void; onRemoveAll?: () => void; } export const FilterStatus: FC = memo(props => { - const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete } = props; - const showRemoveAll = filterActionValuesByType(actionValues, 'filter', lockReadOnlyForDelete).length > 1; + const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete, onAddFilterClick } = props; + const filterCount = actionValues?.filter(a => a.action.keyword === 'filter').length; + const showRemoveAll = actionValues + ? filterActionValuesByType(actionValues, 'filter', lockReadOnlyForDelete).length > 1 + : false; return (
- {actionValues - .sort((a, b) => { - // sort the view actions to the front - if (a.action.keyword !== b.action.keyword) { - return a.action.keyword === 'view' ? -1 : b.action.keyword === 'view' ? 1 : 0; - } + {actionValues && + actionValues + .sort((a, b) => { + // sort the view actions to the front + if (a.action.keyword !== b.action.keyword) { + return a.action.keyword === 'view' ? -1 : b.action.keyword === 'view' ? 1 : 0; + } - // then sort by filter display value - const aDisplayValue = a.displayValue ?? a.value; - const bDisplayValue = b.displayValue ?? b.value; - return aDisplayValue > bDisplayValue ? 1 : aDisplayValue < bDisplayValue ? -1 : 0; - }) - .map((actionValue, index) => { - // loop over all actionValues so that the index remains consistent, but don't show sort actions - if (actionValue.action.keyword === 'sort') { - return null; - } + // then sort by filter display value + const aDisplayValue = a.displayValue ?? a.value; + const bDisplayValue = b.displayValue ?? b.value; + return aDisplayValue > bDisplayValue ? 1 : aDisplayValue < bDisplayValue ? -1 : 0; + }) + .map((actionValue, index) => { + // loop over all actionValues so that the index remains consistent, but don't show sort actions + if (actionValue.action.keyword === 'sort') { + return null; + } - // only FilterActions can be edited via click - const _onClick = actionValue.action.keyword === 'filter' ? onClick : undefined; - // search and filter actions can be removed via click - const _onRemove = - actionValue.action.keyword === 'filter' || actionValue.action.keyword === 'search' - ? onRemove - : undefined; + // only FilterActions can be edited via click + const _onClick = actionValue.action.keyword === 'filter' ? onClick : undefined; + // search and filter actions can be removed via click + const _onRemove = + actionValue.action.keyword === 'filter' || actionValue.action.keyword === 'search' + ? onRemove + : undefined; - return ( - - ); - })} + return ( + + ); + })} + + {onAddFilterClick && ( + + )} {onRemoveAll && showRemoveAll && ( Remove all @@ -63,3 +80,4 @@ export const FilterStatus: FC = memo(props => {
); }); +FilterStatus.displayName = 'FilterStatus'; diff --git a/packages/components/src/public/QueryModel/GridPanel.tsx b/packages/components/src/public/QueryModel/GridPanel.tsx index f626357dd5..d53625f525 100644 --- a/packages/components/src/public/QueryModel/GridPanel.tsx +++ b/packages/components/src/public/QueryModel/GridPanel.tsx @@ -492,12 +492,15 @@ export class GridPanel extends PureComponent, State> { const searchAction = this.gridActions.search.actionValueFromFilter(filter); searchActionValues.push(searchAction); } else { - const column = model.getColumnByFieldKey(filter.getColumnName()); + const filterColName = filter.getColumnName(); + const column = model.getColumnByFieldKey(filterColName); if (column) { actionValues.push(this.gridActions.filter.actionValueFromFilter(filter, column)); - } else if (filter.getColumnName().indexOf('/') > -1) { - const lookupCol = model.getColumnByFieldKey(filter.getColumnName().split('/')[0]); + } else if (filterColName.indexOf('/') > -1 && filterColName.split('/').length === 2) { + const lookupCol = model.getColumnByFieldKey(filterColName.split('/')[0]); if (lookupCol) actionValues.push(this.gridActions.filter.actionValueFromFilter(filter, lookupCol)); + } else { + actionValues.push(this.gridActions.filter.actionValueFromFilter(filter)); } } });