diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 41abb65a8c..e259b11e59 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.14.0", + "version": "7.14.1-jobActionsUiUpdate.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.14.0", + "version": "7.14.1-jobActionsUiUpdate.1", "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 7b6e33ec3e..7088f18715 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.14.0", + "version": "7.14.1-jobActionsUiUpdate.1", "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 a30bafbb87..340a7fb20b 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.14.0 *Released*: 30 January 2026 - Update `withQueryModels` to track and cancel requests for `loadRows`, `loadSelections` and `loadTotalCount` 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';