Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a cot formatter filter #6254

Draft
wants to merge 10 commits into
base: production
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type QueryFieldType =
| 'id'
| 'number'
| 'text';
export type QueryFieldFilter =
export type QueryFieldFilterType =
| 'ageName'
| 'ageRange'
| 'any'
Expand All @@ -65,7 +65,7 @@ export type QueryFieldFilter =
| 'startsWith'
| 'true'
| 'trueOrNull';
export const filtersWithDefaultValue = new Set<QueryFieldFilter>([
export const filtersWithDefaultValue = new Set<QueryFieldFilterType>([
'equal',
'in',
]);
Expand Down Expand Up @@ -401,7 +401,7 @@ function In({
}

export const queryFieldFilters: RR<
QueryFieldFilter,
QueryFieldFilterType,
{
readonly id: number;
readonly label: LocalizedString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,68 @@ import React from 'react';

import { commonText } from '../../localization/common';
import { queryText } from '../../localization/query';
import type { RA } from '../../utils/types';
import { RA } from '../../utils/types';
import { Button } from '../Atoms/Button';
import { className } from '../Atoms/className';
import { iconClassName, icons } from '../Atoms/Icons';
import {
mappingElementDivider,
mappingElementDividerClassName,
} from '../WbPlanView/LineComponents';
import type { QueryFieldFilter } from './FieldFilter';
import type { QueryField } from './helpers';

type FieldFilterToolProps = {
readonly fieldFilters: RA<{
readonly type: QueryFieldFilter;
readonly startValue: string;
readonly isNot: boolean;
/**
* When 'isStrict' is True
* each CO's age_range must be fully contained by
* the age range provided by start_time and end_time filter (complete overlap).
* When 'isStrict' is False, only a partial overlap
* between a CO's age range and the provided
* start_time and end_time filter are needed.
*/
readonly isStrict: boolean;
}>;
readonly index: number;
readonly isBasic: boolean;
readonly hasAny: boolean;
readonly isFieldComplete: boolean;
readonly fieldName: string;
readonly handleChange: ((newField: QueryField) => void) | undefined;
readonly handleFilterChange: (
index: number,
filter: QueryField['filters'][number] | undefined
) => void;
};
import { QueryFieldFilterType } from './FieldFilter';
import { QueryFieldFilterProps } from './QueryLineFieldFilter';

/**
* Buttons relating to the management of the filter or change the behavior
* of the filter
*
* Examples of these buttons are:
*
* Add Filter Button, Negate Filter Button, Remove Filter Button,
* Toggle IsStrict Button (for CollectionObject -> Age queries)
*/
export function FieldFilterTool({
fieldFilters,
index,
fieldFilter,
isFirst,
isBasic,
hasAny,
hasMultipleFilters,
isFieldComplete,
fieldName,
handleChange,
handleFilterChange,
}: FieldFilterToolProps): JSX.Element {
onChange: handleChange,
onAddFieldFilter: handleAddFieldFilter,
onRemoveFieldFilter: handleRemoveFieldFilter,
}: QueryFieldFilterProps): JSX.Element {
return (
<>
{index === 0 ? (
{/* REFACTOR: Extract this to separate component */}
{isFirst ? (
<>
{isBasic ? null : mappingElementDivider}
{!hasAny && (
<Button.Small
aria-label={queryText.or()}
aria-pressed={fieldFilters.length > 1}
aria-pressed={hasMultipleFilters}
className={`
print:hidden
${className.ariaHandled}
${isFieldComplete ? '' : 'invisible'}
`}
disabled={handleChange === undefined}
title={queryText.or()}
variant={
fieldFilters.length > 1
hasMultipleFilters
? className.infoButton
: className.secondaryLightButton
}
onClick={(): void =>
handleFilterChange(fieldFilters.length, {
type: 'any',
isNot: false,
startValue: '',
isStrict: false,
})
onClick={
handleAddFieldFilter === undefined
? undefined
: (): void =>
handleAddFieldFilter({
type: 'any',
isNot: false,
startValue: '',
isStrict: false,
})
}
>
{icons.plus}
Expand All @@ -99,63 +85,82 @@ export function FieldFilterTool({
<Button.Small
aria-label={commonText.remove()}
className="print:hidden"
disabled={handleChange === undefined}
title={commonText.remove()}
variant={className.dangerButton}
onClick={(): void => handleFilterChange(index, undefined)}
onClick={
handleRemoveFieldFilter === undefined
? undefined
: (): void => handleRemoveFieldFilter()
}
>
{icons.trash}
</Button.Small>
</>
)}
{fieldFilters[index].type !== 'any' && (
{fieldFilter.type !== 'any' && (
<Button.Small
aria-label={queryText.negate()}
aria-pressed={fieldFilters[index].isNot}
aria-pressed={fieldFilter.isNot}
className={className.ariaHandled}
disabled={handleChange === undefined}
title={queryText.negate()}
variant={
fieldFilters[index].isNot
fieldFilter.isNot
? className.dangerButton
: className.secondaryLightButton
}
onClick={(): void =>
handleFilterChange(index, {
...fieldFilters[index],
isNot: !fieldFilters[index].isNot,
})
onClick={
handleChange === undefined
? undefined
: (): void =>
handleChange({
...fieldFilter,
isNot: !fieldFilter.isNot,
})
}
>
{icons.ban}
</Button.Small>
)}
{fieldName === 'age' && index === 0 ? (
<Button.Small
aria-label={
fieldFilters[index].isStrict
? queryText.strict()
: queryText.nonStrict()
}
aria-pressed={fieldFilters[index].isStrict}
className={className.ariaHandled}
disabled={handleChange === undefined}
title={
fieldFilters[index].isStrict
? queryText.strict()
: queryText.nonStrict()
}
variant={className.secondaryLightButton}
onClick={(): void =>
handleFilterChange(index, {
...fieldFilters[index],
isStrict: !fieldFilters[index].isStrict,
})
}
>
{fieldFilters[index].isStrict ? icons.strict : icons.nonStrict}
</Button.Small>
{/**
* REFACTOR: Add a property to queryFieldFilters to determine
* FieldFilterTool component based on type
*/}
{(['ageRange', 'ageName'] as RA<QueryFieldFilterType>).includes(
fieldFilter.type
) ? (
<AgeQueryFieldFilter
fieldFilter={fieldFilter}
onChange={handleChange}
/>
) : undefined}
</>
);
}

function AgeQueryFieldFilter({
fieldFilter,
onChange: handleChange,
}: Pick<QueryFieldFilterProps, 'fieldFilter' | 'onChange'>): JSX.Element {
return (
<Button.Small
aria-label={
fieldFilter.isStrict ? queryText.strict() : queryText.nonStrict()
}
aria-pressed={fieldFilter.isStrict}
className={className.ariaHandled}
title={fieldFilter.isStrict ? queryText.strict() : queryText.nonStrict()}
variant={className.secondaryLightButton}
onClick={
handleChange === undefined
? undefined
: () =>
handleChange({
...fieldFilter,
isStrict: !fieldFilter.isStrict,
})
}
>
{fieldFilter.isStrict ? icons.strict : icons.nonStrict}
</Button.Small>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ export function QueryFields({
const fieldsRef = React.useRef(fields);
fieldsRef.current = fields;

const handleChangeFieldRef = React.useRef(handleChangeFields);
handleChangeFieldRef.current = handleChangeFields;
const handleChangeFieldsRef = React.useRef(handleChangeFields);
handleChangeFieldsRef.current = handleChangeFields;

// Draggable and sortable code
React.useEffect(() => {
if (handleChangeFieldRef.current === undefined) return;
if (handleChangeFieldsRef.current === undefined) return;

if (fieldsContainerRef.current === null) return;
const sortable = new Sortable(fieldsContainerRef.current, {
Expand Down Expand Up @@ -127,7 +127,7 @@ export function QueryFields({
newItems.splice(oldIndex + 1, 1);
}

handleChangeFieldRef.current?.(newItems);
handleChangeFieldsRef.current?.(newItems);
handleLineFocus?.(newIndex);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,36 +99,41 @@ export function CatalogNumberFormatSelection({
)
.then((cots) => {
const formattersMap = filterArray(cots).reduce((map, cot) => {
const format = cot.get('catalogNumberFormatName') ?? schema.catalogNumFormatName;
const format =
cot.get('catalogNumberFormatName') ??
schema.catalogNumFormatName;
const cotName = cot.get('name');

if (!map.has(format)) {
map.set(format, {
name: format,
title: format,
isDefault: format === schema.catalogNumFormatName,
cotNames: []
cotNames: [],
});
}

const formatter = map.get(format)!;
map.set(format, {
...formatter,
cotNames: [...formatter.cotNames, cotName]
cotNames: [...formatter.cotNames, cotName],
});
return map;
}, new Map<string, FormatterWithCOTs>());

return Array.from(formattersMap.values(), ({ name, isDefault, cotNames }) => {
const title = queryText.formatInputAs({
commaSeparatedFormats: cotNames.join(', '),
});
return {
name,
title,
isDefault
};
});

return Array.from(
formattersMap.values(),
({ name, isDefault, cotNames }) => {
const title = queryText.formatInputAs({
commaSeparatedFormats: cotNames.join(', '),
});
return {
name,
title,
isDefault,
};
}
);
}),
[]
),
Expand Down Expand Up @@ -165,7 +170,7 @@ function FormatSelect({
readonly onChange: ((formatter: string | undefined) => void) | undefined;
}): JSX.Element | null {
const [formatterSelectIsOpen, setFormatterSelect] = React.useState(false);

const id = useId('formatters-selection');

return availableFormatters === undefined ? (
Expand Down Expand Up @@ -211,7 +216,7 @@ function FormatSelect({
!availableFormatters
.map(({ name }) => name)
.includes(currentFormat) ? (
<option key="invalidCOT" value={currentFormat}>
<option key="invalidFormatter" value={currentFormat}>
{queryText.invalidPicklistValue({ value: currentFormat })}
</option>
) : undefined}
Expand Down
Loading
Loading