Skip to content

Commit

Permalink
Merge pull request #3540 from LiteFarmOrg/LF-4474-nice-to-have-conver…
Browse files Browse the repository at this point in the history
…t-create-task-button-to-cta-floating-button

LF-4474 [Nice to have] convert create task button to CTA floating button
  • Loading branch information
Duncan-Brain authored Nov 27, 2024
2 parents 42f671b + 9f86408 commit 273f1f7
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 83 deletions.
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;
}

0 comments on commit 273f1f7

Please sign in to comment.