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

LF-3637(3): Update the date inputs used in date range selector [integration] #2913

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
223ad3c
LF-3637 create useDateRangeSelector hook
SayakaOno Oct 4, 2023
567a3d8
LF-3637 update FinanceDateRangeSelector to use the new DateRangeSelector
SayakaOno Oct 4, 2023
51c9524
LF-3637 use useDateRangeSelector in Finances and OtherExpense
SayakaOno Oct 4, 2023
1870284
LF-3637 update Labour to use the new DateRangeSelector functionality
SayakaOno Oct 5, 2023
33724ce
LF-3637 update ActualRevenue and EstimatedRevenue to use DateRangeSel…
SayakaOno Oct 5, 2023
152603e
LF-3637 delete LegacyEstimatedRevenue component and its route
SayakaOno Oct 5, 2023
21419ac
LF-3637 remove unused prop in FinanceDateRangeSelector
SayakaOno Oct 5, 2023
780ddf6
LF-3637 fix file names' extension
SayakaOno Oct 17, 2023
878974a
LF-3637 tweaks and bug fix
SayakaOno Oct 18, 2023
df08a52
LF-3637 update date_range state structure
SayakaOno Oct 20, 2023
5cf65ec
LF-3637 fix dateRangeSelector formatOptionLabel function and story
SayakaOno Oct 20, 2023
3495ba3
LF-3637 fix DateRangeSelector and update Labour
SayakaOno Oct 20, 2023
d680a10
LF-3637 cleanup
SayakaOno Oct 20, 2023
19186b5
LF-3637 fix bugs
SayakaOno Oct 24, 2023
a551c95
LF-3637 add tests for DateRangeSelector story
SayakaOno Oct 24, 2023
c8e98b8
LF-3637 rename dateRangeSelector for Redux
SayakaOno Oct 24, 2023
7858554
Merge branch 'integration' into LF-3637_3_2/Update_the_Date_inputs_us…
SayakaOno Oct 24, 2023
d2dfb39
LF-3637 fix ReactSelect's isSearchable
SayakaOno Oct 24, 2023
e0deb88
LF-3637 disable search functionality in DateRangeSelector
SayakaOno Oct 24, 2023
65883bd
LF-3637 tweaks on useEffect cleanup in CustomDateRangeSelector
SayakaOno Oct 24, 2023
c693422
Merge branch 'integration' into LF-3637_3_2/Update_the_Date_inputs_us…
SayakaOno Oct 25, 2023
bd78b95
LF-3637 update date placeholder in DateRangeSelector
SayakaOno Oct 25, 2023
8c370c0
LF-3637 change the way to reset date range option
SayakaOno Oct 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 0 additions & 5 deletions packages/webapp/src/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ const AddSale = React.lazy(() => import('./containers/Finances/AddSale'));
const ManageRevenueTypes = React.lazy(() =>
import('./containers/Finances/ManageCustomRevenueTypes'),
);
const LegacyEstimatedRevenue = React.lazy(() =>
import('./containers/Finances/LegacyEstimatedRevenue'),
);
const EstimatedRevenue = React.lazy(() => import('./containers/Finances/EstimatedRevenue'));
const Labour = React.lazy(() => import('./containers/Finances/Labour'));
const OtherExpense = React.lazy(() => import('./containers/Finances/OtherExpense'));
Expand Down Expand Up @@ -603,7 +600,6 @@ const Routes = () => {
<Route path="/manage_custom_revenues" exact component={ManageRevenueTypes} />
<Route path="/revenue/:sale_id/" exact component={RevenueDetail} />
<Route path="/revenue/:sale_id/edit" exact component={RevenueDetail} />
<Route path="/temp_estimated_revenue" exact component={LegacyEstimatedRevenue} />
<Route path="/estimated_revenue" exact component={EstimatedRevenue} />
<Route path="/labour" exact component={Labour} />
<Route path="/other_expense" exact component={OtherExpense} />
Expand Down Expand Up @@ -909,7 +905,6 @@ const Routes = () => {
<Route path="/manage_custom_revenues" exact component={ManageRevenueTypes} />
<Route path="/revenue/:sale_id/" exact component={RevenueDetail} />
<Route path="/revenue/:sale_id/edit" exact component={RevenueDetail} />
<Route path="/temp_estimated_revenue" exact component={LegacyEstimatedRevenue} />
<Route path="/estimated_revenue" exact component={EstimatedRevenue} />
<Route path="/labour" exact component={Labour} />
<Route path="/other_expense" exact component={OtherExpense} />
Expand Down
36 changes: 11 additions & 25 deletions packages/webapp/src/components/DateRangeSelector/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,17 @@ import styles from './styles.module.scss';
export default function DateRangeSelector({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing I didn't notice before -- there is a slight but handled naming conflict with dateRangeSelector from the state selectors in Finances. /components/Finances/DateRangeSelector/index.jsx is at least one file that imports both {dateRangeSelector} and DateRangeSelector.

I don't have any great name alternatives haha

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it known to cause any problems??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a best practice to avoid having the same name in one file. The main problem with non unique names is developer confusion (in javascript). As you know this will work. Communication becomes harder.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood.

defaultDateRangeOptionValue,
defaultCustomDateRange = {},
setDateRangeOptionValue,
onChangeDateRangeOption,
placeholder,
changeDateMethod,
dateRange,
}) {
const [isCustomDatePickerOpen, setIsCustomDatePickerOpen] = useState(false);

const { t } = useTranslation();
const selectRef = useRef(null);

const [customFromDate, setCustomFromDate] = useState(undefined);
const [customToDate, setCustomToDate] = useState(undefined);
const [customFromDate, setCustomFromDate] = useState(defaultCustomDateRange[FROM_DATE]);
const [customToDate, setCustomToDate] = useState(defaultCustomDateRange[TO_DATE]);

const isValidRange = customFromDate <= customToDate;
const areValidDates = customFromDate?.isValid() && customToDate?.isValid();
Expand All @@ -56,22 +55,11 @@ export default function DateRangeSelector({
{ value: rangeOptions.CUSTOM, label: t('DATE_RANGE_SELECTOR.CUSTOM_RANGE') },
];

// set defaultCustomDateRange if exists
useEffect(() => {
if (defaultDateRangeOptionValue !== rangeOptions.CUSTOM) {
return;
}
const { [FROM_DATE]: defaultFromDate, [TO_DATE]: defaultToDate } = defaultCustomDateRange;

// if the range does not have both FROM_DATE and TO_DATE, reset the option to "Year to date"
if (!defaultFromDate || !defaultToDate) {
if (!isValid & !isCustomDatePickerOpen) {
setSelectedDateRangeOption(options[0]);
return;
}

setCustomFromDate(defaultFromDate);
setCustomToDate(defaultToDate);
}, []);
}, [isValid, isCustomDatePickerOpen]);

const formatOptionLabel = (data, formatOptionLabelMeta) => {
if (formatOptionLabelMeta.context === 'menu') {
Expand All @@ -88,7 +76,7 @@ export default function DateRangeSelector({
.map((date) => date.format('YYYY-MM-DD'))
.join(' - ');
} else {
formattedOption = 'yyyy-mm-dd - yyyy-mm-dd';
formattedOption = 'yyyy.mm.dd - yyyy.mm.dd';
className = styles.invalid;
}
}
Expand All @@ -102,7 +90,7 @@ export default function DateRangeSelector({
};

const setSelectedDateRangeOption = (option) => {
selectRef.current.setValue(option);
selectRef.current?.setValue(option);
};

const clearCustomDateRange = () => {
Expand All @@ -111,12 +99,9 @@ export default function DateRangeSelector({
};

const onClickAway = () => {
if (dateRange.option !== rangeOptions.CUSTOM) {
if (!isCustomDatePickerOpen) {
return;
}
if (!isValid) {
setSelectedDateRangeOption(options[0]);
}
setIsCustomDatePickerOpen(false);
};

Expand Down Expand Up @@ -148,13 +133,14 @@ export default function DateRangeSelector({
if (e?.value === rangeOptions.CUSTOM) {
setIsCustomDatePickerOpen(true);
}
setDateRangeOptionValue && e?.value && setDateRangeOptionValue(e.value);
onChangeDateRangeOption && e?.value && onChangeDateRangeOption(e.value);
}}
formatOptionLabel={formatOptionLabel}
defaultValue={
defaultDateRangeOptionValue &&
options.find(({ value }) => value === defaultDateRangeOptionValue)
}
isSearchable={false}
/>
{isCustomDatePickerOpen && (
<CustomDateRangeSelector
Expand Down Expand Up @@ -183,5 +169,5 @@ DateRangeSelector.propTypes = {
}),
placeholder: PropTypes.string,
changeDateMethod: PropTypes.func,
setDateRangeOptionValue: PropTypes.func,
onChangeDateRangeOption: PropTypes.func,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2023 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { dateRangeDataSelector } from '../../containers/Finances/selectors';
import DateRange, { MONDAY, SUNDAY } from '../../util/dateRange';
import { dateRangeOptions } from './constants';
import { setDateRange } from '../../containers/Finances/actions';

/**
* Returns startDate and endDate in the store.
* If they are not defined, set the default option and dates.
*/
export default function useDateRangeSelector({ weekStartDate = SUNDAY }) {
const dateRange = useSelector(dateRangeDataSelector);
const dispatch = useDispatch();

const dateRangeUtil = new DateRange(new Date(), weekStartDate);
const option = dateRange.option || dateRangeOptions.YEAR_TO_DATE;

useEffect(() => {
if ((!dateRange.startDate || !dateRange.endDate) && option !== dateRangeOptions.CUSTOM) {
const { startDate, endDate } = dateRangeUtil.getDates(option);
dispatch(setDateRange({ option, startDate, endDate }));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we set this start date and end date as the initial state on the finance reducer? That way we probably wouldn't need this hook, and we would have the default state setup on the first render.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible, but we might want to change "start date" of the week (Sunday or Monday) depending on the user in the future, so I decided not to set the default in the reducer.

}
}, []);

return { startDate: dateRange.startDate, endDate: dateRange.endDate };
}

useDateRangeSelector.propTypes = {
weekStartDate: PropTypes.oneOf([SUNDAY, MONDAY]),
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,61 @@
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

import React, { useState } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styles from './styles.module.scss';
import { FromToDateContainer } from '../../../components/Inputs/DateContainer';
import DateRangeSelector from '../../DateRangeSelector';
import { setDateRange } from '../../../containers/Finances/actions';
import moment from 'moment';
import InfoBoxComponent from '../../InfoBoxComponent';
import { dateRangeSelector } from '../../../containers/Finances/selectors';
import { dateRangeDataSelector } from '../../../containers/Finances/selectors';
import { Semibold } from '../../Typography';
import { useTranslation } from 'react-i18next';
import { FROM_DATE, TO_DATE } from '../../Form/DateRangePicker';
import { dateRangeOptions } from '../../DateRangeSelector/constants';
import DateRange, { SUNDAY } from '../../../util/dateRange';

const DateRangeSelector = ({ changeDateMethod, hideTooltip }) => {
const isDateValid = (date) => {
return date ? moment(date).isValid() : false;
};

const FinanceDateRangeSelector = ({ hideTooltip }) => {
const { t } = useTranslation();
const dispatch = useDispatch();

const dateRange = useSelector(dateRangeSelector);
const dateRange = useSelector(dateRangeDataSelector);
const { option, customRange = {} } = dateRange;
const initialOption = option || dateRangeOptions.YEAR_TO_DATE;
const dateRangeUtil = new DateRange(new Date(), SUNDAY);

let initialStartDate, initialEndDate;
if (dateRange && dateRange.startDate && dateRange.endDate) {
initialStartDate = moment(dateRange.startDate);
initialEndDate = moment(dateRange.endDate);
} else {
initialStartDate = moment().startOf('year');
initialEndDate = moment().endOf('year');
}
const initialStartDate = customRange.startDate && moment(customRange.startDate);
const initialEndDate = customRange.endDate && moment(customRange.endDate);

const [startDate, setStartDate] = useState(initialStartDate);
const [endDate, setEndDate] = useState(initialEndDate);
const [validRange, setValidRange] = useState(initialStartDate <= initialEndDate);
const changeDate = (type, date) => {
const startDate = type === 'start' ? date : customRange.startDate;
const endDate = type === 'end' ? date : customRange.endDate;

const changeStartDate = (date) => {
if (date > endDate) {
setValidRange(false);
return;
const newDateRange = { customRange: { startDate, endDate } };

// If both dates are valid, update dates and the option
if ([startDate, endDate].every(isDateValid)) {
newDateRange.startDate = startDate;
newDateRange.endDate = endDate;
}
setValidRange(true);
setStartDate(date);
dispatch(setDateRange({ startDate: date, endDate }));
changeDateMethod('start', date);
dispatch(setDateRange(newDateRange));
};

const changeEndDate = (date) => {
if (date < startDate) {
setValidRange(false);
return;
const onChangeDateRangeOption = (value) => {
let newDateRange = {};
if (value !== dateRangeOptions.CUSTOM) {
newDateRange = dateRangeUtil.getDates(value);
} else if (
Object.keys(customRange).length === 2 &&
Object.values(customRange).every((date) => date && isDateValid(date))
) {
newDateRange = customRange;
}
setValidRange(true);
setEndDate(date);
dispatch(setDateRange({ startDate, endDate: date }));
changeDateMethod('end', date);
dispatch(setDateRange({ option: value, ...newDateRange }));
};

return (
Expand All @@ -80,19 +85,14 @@ const DateRangeSelector = ({ changeDateMethod, hideTooltip }) => {
)}
</div>

<FromToDateContainer
onStartDateChange={changeStartDate}
onEndDateChange={changeEndDate}
endDate={endDate}
startDate={startDate}
<DateRangeSelector
defaultDateRangeOptionValue={initialOption}
defaultCustomDateRange={{ [FROM_DATE]: initialStartDate, [TO_DATE]: initialEndDate }}
onChangeDateRangeOption={onChangeDateRangeOption}
changeDateMethod={changeDate}
/>
{!validRange && (
<Semibold style={{ textAlign: 'center', color: 'red' }}>
{t('DATE_RANGE.INVALID_RANGE_MESSAGE')}
</Semibold>
)}
</div>
);
};

export default DateRangeSelector;
export default FinanceDateRangeSelector;
4 changes: 2 additions & 2 deletions packages/webapp/src/components/Form/ReactSelect/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ const ReactSelect = React.forwardRef(function ReactSelect(
),
...components,
}}
isSearchable={options?.length > 8 || isSearchable}
isSearchable={isSearchable ?? options?.length > 8}
ref={ref}
defaultValue={defaultValue}
isDisabled={isDisabled}
Expand Down Expand Up @@ -266,7 +266,7 @@ const ReactSelect = React.forwardRef(function ReactSelect(
),
...components,
}}
isSearchable={options?.length > 8 || isSearchable}
isSearchable={isSearchable ?? options?.length > 8}
ref={ref}
defaultValue={defaultValue}
isDisabled={isDisabled}
Expand Down