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-4474 [Nice to have] convert create task button to CTA floating button #3540

Open
wants to merge 9 commits into
base: integration
Choose a base branch
from
10 changes: 8 additions & 2 deletions packages/end-to-end/cypress/e2e/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ describe('Tasks', () => {
cy.contains(translation['MENU']['TASKS']).should('exist').click();
cy.waitForReact();

cy.contains(translation['TASK']['ADD_TASK']).should('exist').and('not.be.disabled').click();
cy.get(`[aria-label="${translation['TASK']['ADD_TASK']}"]`)
.should('exist')
.and('not.be.disabled')
.click();
cy.waitForReact();
cy.get(Selectors.TASK_SELECTION).contains(tasks['CLEANING_TASK']).should('exist').click();

Expand Down Expand Up @@ -92,7 +95,10 @@ describe('Tasks', () => {
it('it should successfully create a field work task', () => {
cy.contains(translation['MENU']['TASKS']).should('exist').click();
cy.waitForReact();
cy.contains(translation['TASK']['ADD_TASK']).should('exist').and('not.be.disabled').click();
cy.get(`[aria-label="${translation['TASK']['ADD_TASK']}"]`)
.should('exist')
.and('not.be.disabled')
.click();
cy.waitForReact();
cy.get(Selectors.TASK_SELECTION)
.contains(tasks['FIELD_WORK_TASK'])
Expand Down
19 changes: 9 additions & 10 deletions packages/webapp/src/components/Animals/Inventory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import ClearFiltersButton, {
} from '../../../components/Button/ClearFiltersButton';
import type { AnimalInventory } from '../../../containers/Animals/Inventory/useAnimalInventory';
import AnimalsFilter from '../../../containers/Animals/AnimalsFilter';
import FloatingButtonMenu from '../../Menu/FloatingButtonMenu';
import FloatingActionButton from '../../Button/FloatingActionButton';
import { TableV2Column, TableKind } from '../../Table/types';
import type { Dispatch, SetStateAction } from 'react';
import styles from './styles.module.scss';
Expand Down Expand Up @@ -158,15 +158,14 @@ const PureAnimalInventory = ({
)}
</div>
{isAdmin && !isTaskView && (
<FloatingButtonMenu
type={'add'}
options={[
{
label: t('ADD_ANIMAL.ADD_ANIMALS'),
onClick: () => history.push(ADD_ANIMALS_URL),
},
]}
/>
<div className={styles.ctaButtonWrapper}>
<FloatingActionButton
// @ts-ignore
type={'add'}
onClick={() => history.push(ADD_ANIMALS_URL)}
aria-label={t('ADD_ANIMAL.ADD_ANIMALS')}
/>
</div>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
.searchAndFilterDesktop {
align-items: center;
gap: 8px;
margin-bottom: 16px;
margin-bottom: 16px;
}

.searchAndFilter {
Expand Down Expand Up @@ -80,3 +80,9 @@
.headerClass {
background-color: var(--tableV2Header);
}

.ctaButtonWrapper {
position: fixed;
bottom: 16px;
right: 16px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

.floatingActionButton {
position: relative;
z-index: 100;

@media (hover: hover) {
&:hover {
Expand Down
32 changes: 1 addition & 31 deletions packages/webapp/src/components/Task/TaskCount/index.jsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,14 @@
import React, { useState } from 'react';
import styles from './styles.module.scss';
import { AddLink } from '../../Typography';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { locationsSelector } from '../../../containers/locationSlice';
import LocationCreationModal from '../../LocationCreationModal';

export default function TaskCount({ count, handleAddTask, isAdmin }) {
export default function TaskCount({ count }) {
const { t } = useTranslation();
const locations = useSelector(locationsSelector);
const [createLocation, setCreateLocation] = useState(false);

const dismissLocationCreationModal = () => {
setCreateLocation(false);
};

const handleClick = () => {
if (locations.length) {
handleAddTask();
} else {
setCreateLocation(true);
}
};

return (
<div className={styles.taskCountContainer}>
<div data-cy="tasks-taskCount" className={styles.taskCount}>
{t('TASK.TASKS_COUNT', { count })}
</div>
<AddLink onClick={handleClick}>{t('TASK.ADD_TASK')}</AddLink>
{createLocation && (
<LocationCreationModal
title={t('LOCATION_CREATION.TASK_TITLE')}
body={
isAdmin ? t('LOCATION_CREATION.TASK_BODY') : t('LOCATION_CREATION.TASK_BODY_WORKER')
}
dismissModal={dismissLocationCreationModal}
isAdmin={isAdmin}
/>
)}
</div>
);
}
112 changes: 73 additions & 39 deletions packages/webapp/src/containers/Task/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import { defaultTaskTypesSelector, userCreatedTaskTypesSelector } from '../taskT
import { getSupportedTaskTypesSet } from '../../components/Task/getSupportedTaskTypesSet';
import { locationsSelector } from '../locationSlice';
import Drawer from '../../components/Drawer';
import FloatingActionButton from '../../components/Button/FloatingActionButton';
import styles from './styles.module.scss';
import LocationCreationModal from '../../components/LocationCreationModal';

export default function TaskPage({ history }) {
const { t } = useTranslation();
Expand All @@ -72,6 +75,20 @@ export default function TaskPage({ history }) {
setIsFilterOpen(true);
};

const [createLocation, setCreateLocation] = useState(false);

const dismissLocationCreationModal = () => {
setCreateLocation(false);
};

const handleAddTask = () => {
if (locations.length) {
onAddTask(dispatch, history, {})();
} else {
setCreateLocation(true);
}
};

const taskTypes = useMemo(() => {
const supportedTaskTypes = getSupportedTaskTypesSet(true);
const taskTypes = [];
Expand Down Expand Up @@ -170,47 +187,64 @@ export default function TaskPage({ history }) {
};
const resetFilter = () => dispatch(clearTasksFilter());
return (
<Layout>
<PageTitle title={t('TASK.PAGE_TITLE')} style={{ paddingBottom: '20px' }} />
<PureTaskDropdownFilter
onDateOrderChange={onDateOrderChange}
isAscending={tasksFilter[IS_ASCENDING]}
onAssigneeChange={onAssigneeChange}
assigneeValue={assigneeValue}
onFilterOpen={onFilterOpen}
isFilterActive={isFilterCurrentlyActive}
/>
<TaskCount
count={taskCardContents.length}
handleAddTask={onAddTask(dispatch, history, {})}
isAdmin={isAdmin}
/>
<Drawer title={t('TASK.FILTER.TITLE')} isOpen={isFilterOpen} onClose={onFilterClose}>
<TasksFilterPage onGoBack={onFilterClose} />
</Drawer>
{isFilterCurrentlyActive && (
<div style={{ marginBottom: '32px' }}>
<ActiveFilterBox pageFilter={tasksFilter} pageFilterKey={'tasks'} />
<div style={{ marginTop: '12px' }}>
<Underlined style={{ color: '#AA5F04' }} onClick={resetFilter}>
{t('FILTER.CLEAR_ALL_FILTERS')}
</Underlined>
<>
<Layout>
<PageTitle title={t('TASK.PAGE_TITLE')} style={{ paddingBottom: '20px' }} />
<PureTaskDropdownFilter
onDateOrderChange={onDateOrderChange}
isAscending={tasksFilter[IS_ASCENDING]}
onAssigneeChange={onAssigneeChange}
assigneeValue={assigneeValue}
onFilterOpen={onFilterOpen}
isFilterActive={isFilterCurrentlyActive}
/>
<TaskCount count={taskCardContents.length} />
<Drawer title={t('TASK.FILTER.TITLE')} isOpen={isFilterOpen} onClose={onFilterClose}>
<TasksFilterPage onGoBack={onFilterClose} />
</Drawer>
{isFilterCurrentlyActive && (
<div style={{ marginBottom: '32px' }}>
<ActiveFilterBox pageFilter={tasksFilter} pageFilterKey={'tasks'} />
<div style={{ marginTop: '12px' }}>
<Underlined style={{ color: '#AA5F04' }} onClick={resetFilter}>
{t('FILTER.CLEAR_ALL_FILTERS')}
</Underlined>
</div>
</div>
</div>
)}
)}

{taskCardContents.length > 0 ? (
taskCardContents.map((task) => (
<TaskCard
key={task.task_id}
onClick={() => history.push(`/tasks/${task.task_id}/read_only`)}
style={{ marginBottom: '14px' }}
{...task}
/>
))
) : (
<Semibold style={{ color: 'var(--teal700)' }}>{t('TASK.NO_TASKS_TO_DISPLAY')}</Semibold>
)}
</Layout>

{taskCardContents.length > 0 ? (
taskCardContents.map((task) => (
<TaskCard
key={task.task_id}
onClick={() => history.push(`/tasks/${task.task_id}/read_only`)}
style={{ marginBottom: '14px' }}
{...task}
/>
))
) : (
<Semibold style={{ color: 'var(--teal700)' }}>{t('TASK.NO_TASKS_TO_DISPLAY')}</Semibold>
{createLocation && (
<LocationCreationModal
title={t('LOCATION_CREATION.TASK_TITLE')}
body={
isAdmin ? t('LOCATION_CREATION.TASK_BODY') : t('LOCATION_CREATION.TASK_BODY_WORKER')
}
dismissModal={dismissLocationCreationModal}
isAdmin={isAdmin}
/>
)}
</Layout>

<div className={styles.ctaButtonWrapper}>
<FloatingActionButton
type={'add'}
onClick={handleAddTask}
aria-label={t('TASK.ADD_TASK')}
/>
</div>
</>
);
}
20 changes: 20 additions & 0 deletions packages/webapp/src/containers/Task/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 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/>.
*/

.ctaButtonWrapper {
position: fixed;
bottom: 16px;
right: 16px;
}
Loading