diff --git a/packages/webapp/public/locales/de/translation.json b/packages/webapp/public/locales/de/translation.json index 4c70b5cc01..ca43ea0fd7 100644 --- a/packages/webapp/public/locales/de/translation.json +++ b/packages/webapp/public/locales/de/translation.json @@ -1875,7 +1875,7 @@ "QUANTITY_CANNOT_EXCEED": "Die genutzte Erntemenge darf nicht größer sein als die zu verteilende Menge", "RATE_THIS_TASK": "Bewerten Sie diese Aufgabe", "REMOVE_HARVEST_USE": "Entfernen", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "Wählen Sie das Datum der Aufgabe", "SELECT_TASK_LOCATIONS": "Wählen Sie die Aufgabenstellung(en)", "SELECT_WILD_CROP": "Diese Aufgabe zielt auf eine Wildpflanze ab", diff --git a/packages/webapp/public/locales/en/translation.json b/packages/webapp/public/locales/en/translation.json index 0319c57172..42f4f07fdb 100644 --- a/packages/webapp/public/locales/en/translation.json +++ b/packages/webapp/public/locales/en/translation.json @@ -2023,7 +2023,7 @@ "QUANTITY_CANNOT_EXCEED": "Harvest quantity used cannot exceed amount to allocate", "RATE_THIS_TASK": "Rate this task", "REMOVE_HARVEST_USE": "Remove", - "SELECT_ANIMALS_TO_MOVE": "Select animals to move", + "SELECT_ANIMALS": "Select animals", "SELECT_DATE": "Select the task date", "SELECT_TASK_LOCATIONS": "Select the task location(s)", "SELECT_WILD_CROP": "This task targets a wild crop", diff --git a/packages/webapp/public/locales/es/translation.json b/packages/webapp/public/locales/es/translation.json index 3e8c679dc1..4df6ff908b 100644 --- a/packages/webapp/public/locales/es/translation.json +++ b/packages/webapp/public/locales/es/translation.json @@ -2022,7 +2022,7 @@ "QUANTITY_CANNOT_EXCEED": "La cantidad de cosecha utilizada no puede exceder la cantidad a asignar", "RATE_THIS_TASK": "Califica esta tarea", "REMOVE_HARVEST_USE": "Quitar", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "Seleccione la fecha de la tarea", "SELECT_TASK_LOCATIONS": "Seleccione la(s) ubicación(es) de la tarea", "SELECT_WILD_CROP": "Esta tarea se dirige a un cultivo silvestre", diff --git a/packages/webapp/public/locales/fr/translation.json b/packages/webapp/public/locales/fr/translation.json index 8da2bf629f..a26f0cbce7 100644 --- a/packages/webapp/public/locales/fr/translation.json +++ b/packages/webapp/public/locales/fr/translation.json @@ -2021,7 +2021,7 @@ "QUANTITY_CANNOT_EXCEED": "La quantité de récolte utilisée ne peut pas dépasser la quantité à allouer", "RATE_THIS_TASK": "Évaluez cette tâche", "REMOVE_HARVEST_USE": "Supprimer", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "Sélectionnez la date de la tâche", "SELECT_TASK_LOCATIONS": "Sélectionnez le(s) emplacement(s) de la tâche", "SELECT_WILD_CROP": "Cette tâche cible une culture sauvage", diff --git a/packages/webapp/public/locales/hi/translation.json b/packages/webapp/public/locales/hi/translation.json index 2286c40d96..c460b00605 100644 --- a/packages/webapp/public/locales/hi/translation.json +++ b/packages/webapp/public/locales/hi/translation.json @@ -1872,7 +1872,7 @@ "QUANTITY_CANNOT_EXCEED": "फसल उपयोग की मात्रा आवंटित राशि से अधिक नहीं हो सकती", "RATE_THIS_TASK": "इस कार्य को रेट करें", "REMOVE_HARVEST_USE": "हटाएं", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "कार्य तिथि चुनें", "SELECT_TASK_LOCATIONS": "कार्य स्थान(ओं) का चयन करें", "SELECT_WILD_CROP": "यह कार्य एक जंगली फसल को लक्षित करता है", diff --git a/packages/webapp/public/locales/pa/translation.json b/packages/webapp/public/locales/pa/translation.json index b3c6e85b48..4046fe7bb5 100644 --- a/packages/webapp/public/locales/pa/translation.json +++ b/packages/webapp/public/locales/pa/translation.json @@ -1872,7 +1872,7 @@ "QUANTITY_CANNOT_EXCEED": "ਵਰਤੇ ਗਏ ਵਾਢੀ ਦੀ ਮਾਤਰਾ ਨਿਰਧਾਰਤ ਕੀਤੀ ਰਕਮ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੋ ਸਕਦੀ", "RATE_THIS_TASK": "ਇਸ ਕੰਮ ਨੂੰ ਦਰਜਾ ਦਿਓ", "REMOVE_HARVEST_USE": "ਹਟਾਓ", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "ਕੰਮ ਦੀ ਮਿਤੀ ਚੁਣੋ", "SELECT_TASK_LOCATIONS": "ਕੰਮ ਦੇ ਟਿਕਾਣੇ ਚੁਣੋ", "SELECT_WILD_CROP": "ਇਹ ਕੰਮ ਜੰਗਲੀ ਫਸਲ ਨੂੰ ਨਿਸ਼ਾਨਾ ਬਣਾਉਂਦਾ ਹੈ", diff --git a/packages/webapp/public/locales/pt/translation.json b/packages/webapp/public/locales/pt/translation.json index 2e0e9a6827..31914e381b 100644 --- a/packages/webapp/public/locales/pt/translation.json +++ b/packages/webapp/public/locales/pt/translation.json @@ -2021,7 +2021,7 @@ "QUANTITY_CANNOT_EXCEED": "A quantidade de colheita usada não pode exceder a quantidade a ser alocada", "RATE_THIS_TASK": "Avalie esta tarefa", "REMOVE_HARVEST_USE": "Remover", - "SELECT_ANIMALS_TO_MOVE": "MISSING", + "SELECT_ANIMALS": "MISSING", "SELECT_DATE": "Selecionar a data da tarefa", "SELECT_TASK_LOCATIONS": "Selecionar o(s) local(is) da tarefa", "SELECT_WILD_CROP": "Esta tarefa é para um cultivo silvestre", diff --git a/packages/webapp/src/components/Task/PureTaskAssignment/index.jsx b/packages/webapp/src/components/Task/PureTaskAssignment/index.jsx index df6b7d906e..aaf4c46dcc 100644 --- a/packages/webapp/src/components/Task/PureTaskAssignment/index.jsx +++ b/packages/webapp/src/components/Task/PureTaskAssignment/index.jsx @@ -31,6 +31,7 @@ const PureTaskAssignment = ({ handleSubmit, getValues, additionalContent, + progress = 86, }) => { const { t } = useTranslation(); const wageOverride = watch(WAGE_OVERRIDE); @@ -109,7 +110,7 @@ const PureTaskAssignment = ({ onCancel={historyCancel} title={t('ADD_TASK.ADD_A_TASK')} cancelModalTitle={t('ADD_TASK.CANCEL')} - value={86} + value={progress} /> { @@ -111,8 +112,8 @@ const PureTaskCrops = ({ .map((management_plan) => management_plan.management_plan_id) .filter((management_plan_id) => managementPlanIds.includes(management_plan_id)) : getValues(MANAGEMENT_PLANS)?.length - ? [getValues(MANAGEMENT_PLANS)?.[0]?.management_plan_id] - : [], + ? [getValues(MANAGEMENT_PLANS)?.[0]?.management_plan_id] + : [], ); const onSelectManagementPlan = (management_plan_id) => { @@ -249,7 +250,7 @@ const PureTaskCrops = ({ onCancel={historyCancel} title={t('ADD_TASK.ADD_A_TASK')} cancelModalTitle={t('ADD_TASK.CANCEL')} - value={57} + value={progress} />
{t('ADD_TASK.AFFECT_PLANS')}
diff --git a/packages/webapp/src/components/Task/PureTaskDetails/index.jsx b/packages/webapp/src/components/Task/PureTaskDetails/index.jsx index 657328925f..867882ad62 100644 --- a/packages/webapp/src/components/Task/PureTaskDetails/index.jsx +++ b/packages/webapp/src/components/Task/PureTaskDetails/index.jsx @@ -15,6 +15,7 @@ import PureIrrigationTask from '../PureIrrigationTask'; import PureSoilAmendmentTask from '../SoilAmendmentTask'; import PureMovementTask from '../MovementTask'; import { defaultValues as soilAmendmentProductDefaultValues } from '../AddSoilAmendmentProducts'; +import { getProgress } from '../../../containers/Task/util'; export default function PureTaskDetails({ handleGoBack, @@ -32,10 +33,11 @@ export default function PureTaskDetails({ }) { const { t } = useTranslation(); const taskType = selectedTaskType.task_translation_key; - const taskName = selectedTaskType.task_name; const isCustomType = !!selectedTaskType.farm_id; const isHarvest = isTaskType(selectedTaskType, 'HARVEST_TASK'); const isIrrigationTask = isTaskType(selectedTaskType, 'IRRIGATION_TASK'); + const isCustomTask = isTaskType(selectedTaskType, 'CUSTOM_TASK'); + const progress = isCustomTask ? getProgress('CUSTOM_TASK', 'task_details') : isHarvest ? 67 : 71; const defaults = { CLEANING_TASK: { cleaning_task: { agent_used: false } }, @@ -134,7 +136,7 @@ export default function PureTaskDetails({ onCancel={historyCancel} title={t('ADD_TASK.ADD_A_TASK')} cancelModalTitle={t('ADD_TASK.CANCEL')} - value={isHarvest ? 67 : 71} + value={progress} />
- {t('TASK.SELECT_ANIMALS_TO_MOVE')} + {t('TASK.SELECT_ANIMALS')}
{ const locationIdsSet = new Set(locations.map(({ location_id }) => location_id)); diff --git a/packages/webapp/src/containers/Animals/Inventory/useAnimalsExist.tsx b/packages/webapp/src/containers/Animals/Inventory/useAnimalsExist.tsx new file mode 100644 index 0000000000..91856f97d0 --- /dev/null +++ b/packages/webapp/src/containers/Animals/Inventory/useAnimalsExist.tsx @@ -0,0 +1,29 @@ +/* + * 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 . + */ +import { useGetAnimalsQuery, useGetAnimalBatchesQuery } from '../../../store/api/apiSlice'; +import useQueries from '../../../hooks/api/useQueries'; + +const useAnimalsExist = () => { + const { data, isLoading } = useQueries([ + { label: 'animals', hook: useGetAnimalsQuery }, + { label: 'animalBatches', hook: useGetAnimalBatchesQuery }, + ]); + + const animalsExistOnFarm = !isLoading && (data.animals.length || data.animalBatches.length); + + return { animalsExistOnFarm }; +}; + +export default useAnimalsExist; diff --git a/packages/webapp/src/containers/Task/TaskAnimalInventory/index.jsx b/packages/webapp/src/containers/Task/TaskAnimalInventory/index.jsx index 97cba122c1..e4a675c393 100644 --- a/packages/webapp/src/containers/Task/TaskAnimalInventory/index.jsx +++ b/packages/webapp/src/containers/Task/TaskAnimalInventory/index.jsx @@ -17,15 +17,23 @@ import PureTaskAnimalInventory from '../../../components/Task/TaskAnimalInventor import { HookFormPersistProvider } from '../../hooks/useHookFormPersist/HookFormPersistProvider'; import { useTheme } from '@mui/styles'; import { useMediaQuery } from '@mui/material'; +import { useIsTaskType } from '../useIsTaskType'; +import { getProgress } from '../util'; function TaskAnimalInventory({ history, location }) { + const isCustomTask = useIsTaskType('CUSTOM_TASK'); + const progress = isCustomTask ? getProgress('CUSTOM_TASK', 'task_animal_selection') : undefined; + const onGoBack = () => { history.back(); }; const onContinue = () => { - history.push('/add_task/task_locations', location?.state); + isCustomTask + ? history.push('/add_task/task_details', location?.state) + : history.push('/add_task/task_locations', location?.state); }; + const theme = useTheme(); const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); @@ -36,6 +44,8 @@ function TaskAnimalInventory({ history, location }) { onContinue={onContinue} history={history} isDesktop={isDesktop} + isRequired={!isCustomTask} + progress={progress} /> ); diff --git a/packages/webapp/src/containers/Task/TaskAssignment/index.jsx b/packages/webapp/src/containers/Task/TaskAssignment/index.jsx index a3cd81c759..068583e250 100644 --- a/packages/webapp/src/containers/Task/TaskAssignment/index.jsx +++ b/packages/webapp/src/containers/Task/TaskAssignment/index.jsx @@ -19,6 +19,8 @@ import { ASSIGNEE, ALREADY_COMPLETED, } from '../../../components/Task/AssignTask/constants'; +import { getProgress } from '../util'; +import { useIsTaskType } from '../useIsTaskType'; export default function TaskManagement({ history, match, location }) { const userFarms = useSelector(userFarmEntitiesSelector); @@ -31,7 +33,8 @@ export default function TaskManagement({ history, match, location }) { const persistedFormData = useSelector(hookFormPersistSelector); const [isFarmWorker] = useState(userFarm.role_id === 3); const worker = users[userFarm.user_id]; - + const isCustomTask = useIsTaskType('CUSTOM_TASK'); + const progress = isCustomTask ? getProgress('CUSTOM_TASK', 'task_assignment') : undefined; const [showCannotCreateModal, setShowCannotCreateModal] = useState(false); const defaultAssignee = useMemo(() => { @@ -184,6 +187,7 @@ export default function TaskManagement({ history, match, location }) { override={override} {...taskAssignForm} additionalContent={taskCompleted} + progress={progress} /> {showCannotCreateModal && ( diff --git a/packages/webapp/src/containers/Task/TaskCrops/index.jsx b/packages/webapp/src/containers/Task/TaskCrops/index.jsx index 9401eba387..6ef979a647 100644 --- a/packages/webapp/src/containers/Task/TaskCrops/index.jsx +++ b/packages/webapp/src/containers/Task/TaskCrops/index.jsx @@ -8,13 +8,20 @@ import { } from './useManagementPlanTilesByLocationIds'; import { cropLocationsSelector } from '../../locationSlice'; import { useIsTaskType } from '../useIsTaskType'; +import { getProgress } from '../util'; +import useAnimalsExist from '../../Animals/Inventory/useAnimalsExist'; export default function ManagementPlanSelector({ history, match, location }) { const isTransplantTask = useIsTaskType('TRANSPLANT_TASK'); + const isCustomTask = useIsTaskType('CUSTOM_TASK'); if (isTransplantTask) return ( ); + if (isCustomTask) + return ( + + ); return ; } @@ -35,6 +42,22 @@ function TransplantManagementPlansSelector({ history, match, location }) { ); } +function CustomTaskManagementPlansSelector({ history, match, location }) { + const { animalsExistOnFarm } = useAnimalsExist(); + const onContinuePath = animalsExistOnFarm ? '/add_task/task_animal_selection' : undefined; + const progress = getProgress('CUSTOM_TASK', 'task_crops'); + + return ( + + ); +} + function TaskCrops({ history, match, @@ -42,6 +65,7 @@ function TaskCrops({ onContinuePath = '/add_task/task_details', locations, location, + progress, }) { const persistedPaths = [goBackPath, onContinuePath]; const handleGoBack = () => { @@ -76,6 +100,7 @@ function TaskCrops({ isRequired={isRequired} wildManagementPlanTiles={showWildCrops ? wildManagementPlanTiles : undefined} defaultManagementPlanId={location?.state?.management_plan_id ?? null} + progress={progress} history={history} location={location} /> diff --git a/packages/webapp/src/containers/Task/TaskDate/index.jsx b/packages/webapp/src/containers/Task/TaskDate/index.jsx index c570dd83a5..416b86bd08 100644 --- a/packages/webapp/src/containers/Task/TaskDate/index.jsx +++ b/packages/webapp/src/containers/Task/TaskDate/index.jsx @@ -4,6 +4,7 @@ import { HookFormPersistProvider } from '../../hooks/useHookFormPersist/HookForm import { useIsTaskType } from '../useIsTaskType'; import { useSelector } from 'react-redux'; import { tasksByManagementPlanIdSelector } from '../../taskSlice'; +import { getProgress } from '../util'; function TaskDate({ history, match, location }) { const onGoBack = () => { @@ -11,6 +12,7 @@ function TaskDate({ history, match, location }) { }; const isTransplantTask = useIsTaskType('TRANSPLANT_TASK'); const isMovementTask = useIsTaskType('MOVEMENT_TASK'); + const isCustomTask = useIsTaskType('CUSTOM_TASK'); const tasks = location.state.management_plan_id ? useSelector(tasksByManagementPlanIdSelector(location.state.management_plan_id)) @@ -73,9 +75,11 @@ function TaskDate({ history, match, location }) { history.push(getNextStepPath(), location?.state); }; + const progress = isCustomTask ? getProgress('CUSTOM_TASK', 'task_date') : undefined; + return ( - + ); } diff --git a/packages/webapp/src/containers/Task/TaskLocations/index.jsx b/packages/webapp/src/containers/Task/TaskLocations/index.jsx index ad80080f0e..1032287bca 100644 --- a/packages/webapp/src/containers/Task/TaskLocations/index.jsx +++ b/packages/webapp/src/containers/Task/TaskLocations/index.jsx @@ -22,6 +22,8 @@ import { useTranslation } from 'react-i18next'; import { useReadOnlyPinCoordinates } from '../useReadOnlyPinCoordinates'; import { useMaxZoom } from '../../Map/useMaxZoom'; import { managementPlanSelector } from '../../managementPlanSlice'; +import { getProgress } from '../util'; +import useAnimalsExist from '../../Animals/Inventory/useAnimalsExist'; export default function TaskLocationsSwitch({ history, match, location }) { const isHarvestLocation = useIsTaskType('HARVEST_TASK'); @@ -52,7 +54,7 @@ export default function TaskLocationsSwitch({ history, match, location }) { } if (isCustomLocation) { - return ; + return ; } return ; @@ -166,7 +168,47 @@ function TaskAnimalLocations({ history, location }) { ); } -function TaskAllLocations({ history, location, optionalLocation }) { +function TaskCustomLocations({ history, location }) { + const dispatch = useDispatch(); + const locations = useSelector(locationsSelector); + const readOnlyPinCoordinates = useReadOnlyPinCoordinates(); + const activeAndCurrentManagementPlansByLocationIds = + useActiveAndCurrentManagementPlanTilesByLocationIds(locations); + const wildManagementPlanTiles = useCurrentWildManagementPlanTiles(); + const { animalsExistOnFarm } = useAnimalsExist(); + + const onContinue = (formData) => { + const hasLocationManagementPlans = formData.locations.some( + ({ location_id }) => activeAndCurrentManagementPlansByLocationIds[location_id]?.length, + ); + const hasWildManagementPlans = formData.show_wild_crop && wildManagementPlanTiles.length; + const hasManagementPlans = hasLocationManagementPlans || hasWildManagementPlans; + if (!readOnlyPinCoordinates?.length || !hasManagementPlans) { + dispatch(setManagementPlansData([])); + if (animalsExistOnFarm) { + return history.push('/add_task/task_animal_selection', location?.state); + } + return history.push('/add_task/task_details', location?.state); + } + history.push('/add_task/task_crops', location?.state); + }; + + const progress = getProgress('CUSTOM_TASK', 'task_locations'); + + return ( + + ); +} + +function TaskAllLocations({ history, location }) { const dispatch = useDispatch(); const locations = useSelector(locationsSelector); const persistedFormData = useSelector(hookFormPersistSelector); @@ -200,7 +242,6 @@ function TaskAllLocations({ history, location, optionalLocation }) { onContinue={onContinue} readOnlyPinCoordinates={readOnlyPinCoordinates} location={location} - optionalLocation={optionalLocation} /> ); } @@ -215,6 +256,7 @@ function TaskLocations({ location, isAnimalTask = false, optionalLocation, + progress, }) { const { grid_points } = useSelector(userFarmSelector); const { maxZoomRef, getMaxZoom, maxZoom } = useMaxZoom(); @@ -242,6 +284,7 @@ function TaskLocations({ targetsWildCrop={managementPlan?.crop_management_plan?.is_wild ?? false} isAnimalTask={isAnimalTask} optionalLocation={optionalLocation} + progress={progress} /> ); diff --git a/packages/webapp/src/containers/Task/constants.js b/packages/webapp/src/containers/Task/constants.js index 23104a7be4..dd077190d2 100644 --- a/packages/webapp/src/containers/Task/constants.js +++ b/packages/webapp/src/containers/Task/constants.js @@ -31,3 +31,16 @@ export const TASK_TYPE_PRODUCT_MAP = { }; export const ANIMAL_TASKS = ['MOVEMENT_TASK']; + +export const TASK_STEPS = { + CUSTOM_TASK: { + task_type_selection: 1, + task_date: 2, + task_locations: 3, + task_crops: 4, + task_animal_selection: 5, + task_details: 6, + task_assignment: 7, + end: 8, + }, +}; diff --git a/packages/webapp/src/containers/Task/util.js b/packages/webapp/src/containers/Task/util.js new file mode 100644 index 0000000000..d4d4e3b227 --- /dev/null +++ b/packages/webapp/src/containers/Task/util.js @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +import { TASK_STEPS } from './constants'; + +export const getProgress = (taskType, stepPath) => { + return TASK_STEPS[taskType] + ? Math.round((100 * TASK_STEPS[taskType][stepPath]) / Object.keys(TASK_STEPS[taskType]).length) + : undefined; +};