Skip to content

Commit

Permalink
feat(faceted/badge): BadgePeriod (#5102)
Browse files Browse the repository at this point in the history
  • Loading branch information
yyanwang authored Jan 19, 2024
1 parent 5269331 commit 941ff4f
Show file tree
Hide file tree
Showing 14 changed files with 596 additions and 55 deletions.
6 changes: 6 additions & 0 deletions .changeset/weak-berries-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@talend/react-faceted-search': minor
'@talend/react-components': patch
---

feat: add BadgePeriod in faceted search
41 changes: 19 additions & 22 deletions packages/components/src/DateTimePickers/shared/error-messages.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,51 @@
/*
* Unexpected issues when importing t directly with named import
* https://github.com/i18next/i18next/issues/1287
* */
// eslint-disable-next-line import/no-named-as-default-member
import i18next from 'i18next';
import getDefaultT from '../../translate';

const t = getDefaultT();

export default function getErrorMessage(key) {
switch (key) {
case 'INVALID_HOUR_EMPTY':
return i18next.t('INVALID_HOUR_EMPTY', { defaultValue: 'Hour is required' });
return t('INVALID_HOUR_EMPTY', { defaultValue: 'Hour is required' });
case 'INVALID_HOUR_NUMBER':
return i18next.t('INVALID_HOUR_NUMBER', { defaultValue: 'Hour must be between 00 and 23' });
return t('INVALID_HOUR_NUMBER', { defaultValue: 'Hour must be between 00 and 23' });
case 'INVALID_MINUTES_EMPTY':
return i18next.t('INVALID_MINUTES_EMPTY', { defaultValue: 'Minutes are required' });
return t('INVALID_MINUTES_EMPTY', { defaultValue: 'Minutes are required' });
case 'INVALID_MINUTES_NUMBER':
return i18next.t('INVALID_MINUTES_NUMBER', {
return t('INVALID_MINUTES_NUMBER', {
defaultValue: 'Minutes value must be between 00 and 59',
});
case 'INVALID_SECONDS_EMPTY':
return i18next.t('INVALID_SECONDS_EMPTY', { defaultValue: 'Seconds are required' });
return t('INVALID_SECONDS_EMPTY', { defaultValue: 'Seconds are required' });
case 'INVALID_SECONDS_NUMBER':
return i18next.t('INVALID_SECONDS_NUMBER', {
return t('INVALID_SECONDS_NUMBER', {
defaultValue: 'Seconds value must be between 00 and 59',
});
case 'INVALID_DATE_FORMAT':
return i18next.t('INVALID_DATE_FORMAT', { defaultValue: 'Date format is invalid' });
return t('INVALID_DATE_FORMAT', { defaultValue: 'Date format is invalid' });
case 'INVALID_DATE_EMPTY':
return i18next.t('INVALID_DATE_EMPTY', { defaultValue: 'Date is required' });
return t('INVALID_DATE_EMPTY', { defaultValue: 'Date is required' });
case 'INVALID_MONTH_NUMBER':
return i18next.t('INVALID_MONTH_NUMBER', { defaultValue: 'Month must be between 01 and 12' });
return t('INVALID_MONTH_NUMBER', { defaultValue: 'Month must be between 01 and 12' });
case 'INVALID_DAY_NUMBER':
return i18next.t('INVALID_DAY_NUMBER', { defaultValue: 'Day is invalid' });
return t('INVALID_DAY_NUMBER', { defaultValue: 'Day is invalid' });
case 'INVALID_DAY_OF_MONTH':
return i18next.t('INVALID_DAY_OF_MONTH', {
return t('INVALID_DAY_OF_MONTH', {
defaultValue: "Day value doesn't match an existing day in the month",
});
case 'INVALID_TIME_EMPTY':
return i18next.t('INVALID_TIME_EMPTY', {
return t('INVALID_TIME_EMPTY', {
defaultValue: 'Time is required',
});
case 'TIME_FORMAT_INVALID':
return i18next.t('TIME_FORMAT_INVALID', { defaultValue: 'Time is invalid' });
return t('TIME_FORMAT_INVALID', { defaultValue: 'Time is invalid' });
case 'DATETIME_INVALID_FORMAT':
return i18next.t('DATETIME_INVALID_FORMAT', { defaultValue: 'Datetime is invalid' });
return t('DATETIME_INVALID_FORMAT', { defaultValue: 'Datetime is invalid' });
case 'INVALID_RANGE_START_AFTER_END':
return i18next.t('INVALID_RANGE_START_AFTER_END', {
return t('INVALID_RANGE_START_AFTER_END', {
defaultValue: 'Start date should comes before end date',
});
case 'INVALID_SELECTED_DATE':
return i18next.t('INVALID_SELECTED_DATE', {
return t('INVALID_SELECTED_DATE', {
defaultValue: 'Invalid date is selected',
});
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';

import classNames from 'classnames';
import { isEqual } from 'lodash';
import PropTypes from 'prop-types';

Expand Down Expand Up @@ -32,6 +33,7 @@ const BadgeFaceted = ({
removable = true,
value,
size = Badge.SIZES.large,
type,
t,
}) => {
const openValueJustAfterSelectionOfType = operators.length < 2 && initialOperatorOpened;
Expand Down Expand Up @@ -104,7 +106,12 @@ const BadgeFaceted = ({
};

return (
<Badge id={id} className={styles['tc-badge-faceted']} display={size} type={displayType}>
<Badge
id={id}
className={classNames(styles['tc-badge-faceted'], type)}
display={size}
type={displayType}
>
{labelCategory && (
<>
<Badge.Category category={labelCategory} label={labelCategory} />
Expand Down Expand Up @@ -162,6 +169,7 @@ BadgeFaceted.propTypes = {
value: PropTypes.any,
readOnly: PropTypes.bool,
removable: PropTypes.bool,
type: PropTypes.string,
t: PropTypes.func.isRequired,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,15 @@
}
}
}


&:global(.period) {
.tc-badge-faceted-overlay {
& > button {
> span > span {
max-width: none;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { useMemo } from 'react';

import { format, isValid } from 'date-fns';
import isEmpty from 'lodash/isEmpty';
import PropTypes from 'prop-types';

import Badge from '@talend/react-components/lib/Badge';

import { operatorPropTypes, operatorsPropTypes } from '../../facetedSearch.propTypes';
import { BadgeFaceted } from '../BadgeFaceted';
import { BadgePeriodForm } from './BadgePeriodForm.component';

const DATE_FORMAT_YYYY_DD_MM = 'yyyy-MM-dd';

function isDateRange(value) {
return value.id === 'CUSTOM';
}

function formatDate(date) {
return format(date, DATE_FORMAT_YYYY_DD_MM);
}

function getRangeLabel(startDateTime, endDateTime, t) {
return `${formatDate(startDateTime)} ${t('TO', { defaultValue: 'to' })} ${formatDate(
endDateTime,
)}`;
}

const getSelectBadgeLabel = (value, t) => {
const labelAll = t('FACETED_SEARCH_VALUE_ALL', { defaultValue: 'All' });

if (isEmpty(value)) return labelAll;

if (isDateRange(value) && isValid(value.startDateTime) && isValid(value.endDateTime)) {
return getRangeLabel(value.startDateTime, value.endDateTime, t);
}

return value.label || labelAll;
};

// eslint-disable-next-line import/prefer-default-export
export const BadgePeriod = ({
displayType,
filterBarPlaceholder,
id,
initialOperatorOpened,
initialValueOpened,
label,
readOnly,
removable,
operator,
operators,
size,
value,
values,
t,
...rest
}) => {
const currentOperator = operator || operators?.[0];
const badgePeriodId = `${id}-badge-period`;
const badgeLabel = useMemo(() => getSelectBadgeLabel(value, t), [value, t]);
return (
<BadgeFaceted
badgeId={id}
displayType={displayType}
id={badgePeriodId}
initialOperatorOpened={initialOperatorOpened}
initialValueOpened={initialValueOpened}
labelCategory={label}
labelValue={badgeLabel}
operator={currentOperator}
operators={operators}
readOnly={readOnly}
removable={removable}
size={size}
t={t}
value={value || {}}
type="period"
>
{({ onSubmitBadge, onChangeValue, badgeValue }) => (
<BadgePeriodForm
id={badgePeriodId}
onChange={onChangeValue}
onSubmit={onSubmitBadge}
value={badgeValue}
values={values}
t={t}
{...rest}
/>
)}
</BadgeFaceted>
);
};

BadgePeriod.propTypes = {
displayType: PropTypes.oneOf(Object.values(Badge.TYPES)),
filterBarPlaceholder: PropTypes.string,
id: PropTypes.string.isRequired,
readOnly: PropTypes.bool,
removable: PropTypes.bool,
label: PropTypes.string,
initialOperatorOpened: PropTypes.bool,
initialValueOpened: PropTypes.bool,
operator: operatorPropTypes,
operators: operatorsPropTypes,
size: PropTypes.oneOf(Object.values(Badge.SIZES)),
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
checked: PropTypes.bool,
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
}),
]),
values: PropTypes.array,
t: PropTypes.func.isRequired,
};

BadgePeriod.defaultProps = {
readOnly: false,
removable: true,
initialOperatorOpened: false,
initialValueOpened: false,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { render, within } from '@testing-library/react';

import getDefaultT from '../../../translate';
import { BadgeFacetedProvider } from '../../context/badgeFaceted.context';
import { BadgePeriod } from './BadgePeriod.component';

const t = getDefaultT();

const operator = {
label: 'My Operator',
name: 'my-operator',
};

const badgeFacetedContextValue = {
onDeleteBadge: jest.fn(),
onHideOperator: jest.fn(),
onSubmitBadge: jest.fn(),
dispatch: jest.fn(),
};

const BadgeWithContext = props => (
<BadgeFacetedProvider value={badgeFacetedContextValue}>
<BadgePeriod {...props} />
</BadgeFacetedProvider>
);
describe('BadgePeriod', () => {
it('should return "All" when there is no value', () => {
// Given
const props = {
id: 'myId',
label: 'My Label',
operator,
operators: [{ id: 'in' }],
t,
};
// When
render(<BadgeWithContext {...props} />);
// Then
expect(
within(document.querySelector('#tc-badge-select-myId-badge-period')).getByText('All'),
).toBeVisible();
});
it('should return "All" when value is empty', () => {
// Given
const props = {
id: 'myId',
label: 'My Label',
operator,
operators: [{ id: 'in' }],
t,
value: {},
};
// When
render(<BadgeWithContext {...props} />);
// Then
expect(
within(document.querySelector('#tc-badge-select-myId-badge-period')).getByText('All'),
).toBeVisible();
});
it('should display range when custom period is selected', () => {
// Given
const props = {
id: 'myId',
label: 'My Label',
operator,
operators: [{ id: 'in' }],
t,
value: {
id: 'CUSTOM',
startDateTime: new Date('2021-01-01'),
endDateTime: new Date('2021-01-02'),
},
};
// When
render(<BadgeWithContext {...props} />);
// Then
expect(
within(document.querySelector('#tc-badge-select-myId-badge-period')).getByText(
'2021-01-01 to 2021-01-02',
),
).toBeVisible();
});
});
Loading

0 comments on commit 941ff4f

Please sign in to comment.