diff --git a/packages/webapp/public/locales/en/common.json b/packages/webapp/public/locales/en/common.json index c0f2fc2963..e733c65ef6 100644 --- a/packages/webapp/public/locales/en/common.json +++ b/packages/webapp/public/locales/en/common.json @@ -47,6 +47,7 @@ "HERE": "here", "INVALID_DATE": "Invalid date", "LOADING": "Loading...", + "MANAGE_ENTITY": "Manage {{entity}}", "MARK_ABANDON": "Abandon", "MARK_COMPLETE": "Mark Complete", "MARK_COMPLETED": "Mark completed", @@ -80,6 +81,7 @@ "SAVE": "Save", "SAVE_CHANGES": "Save Changes", "SEARCH": "Search", + "SEE_ON_MAP": "See on map", "SELECT": "Select", "SELECT_ALL": "Select all", "SELECTED_COUNT": "{{count}} selected", diff --git a/packages/webapp/public/locales/en/translation.json b/packages/webapp/public/locales/en/translation.json index 4fa8609e16..662a7a8048 100644 --- a/packages/webapp/public/locales/en/translation.json +++ b/packages/webapp/public/locales/en/translation.json @@ -1789,6 +1789,8 @@ "BRAND": "Brand", "BRAND_TOOLTIP": "Brands that LiteFarm can integrate with are shown below. If you would no longer like to use this sensor brand, try retiring this sensor instead.", "DEPTH": "Depth", + "DEVICE_TYPE": "Device type", + "DITECTED_FIELD": "We detected the field location to be:", "EDIT": "Edit", "EXTERNAL_ID_TOOLTIP": "This id is used to uniquely identify this sensor in other, integrated systems and cannot be changed. If it is no longer being used, try retiring this sensor and adding a new one.", "EXTERNAL_IDENTIFIER": "External Identifier", @@ -1796,7 +1798,18 @@ "LONGITUDE": "Longitude", "MODEL": "Model", "NAME": "Sensor Name", - "RETIRE": "Retire" + "RETIRE": "Retire", + "SEE_FULL_SENSOR_SETUP": "See full sensor setup" + }, + "ESCI": { + "ACTIVE_CONNECTION": "You have an active ESCI connection", + "CONNECT_NEW_SENSOR": "Connect a new sensor setup from ESCI", + "CURRENT_SUPPORT": "We currently only support sensors from \"Ensemble Scientific\"", + "ENSEMBLE_ESID": "Ensemble ESID", + "ENTER_ID": "Enter your Ensemble Scientific organisation ID", + "ORGANISATION_ID": "ESCI organisation ID", + "ORGANISATION_ID_ERROR": "Invalid Organisation ID", + "ORGANISATION_ID_GENERIC_ERROR": "Failed to connect to ESCI. Please try again later." }, "EXTERNAL_IDENTIFIER": "External identifier", "HOURS_AGO": "{{time}} hour(s) ago", diff --git a/packages/webapp/src/apiConfig.js b/packages/webapp/src/apiConfig.js index 74e1ae1e92..b385be27ad 100644 --- a/packages/webapp/src/apiConfig.js +++ b/packages/webapp/src/apiConfig.js @@ -91,6 +91,7 @@ export const soilAmendmentMethodsUrl = `${URI}/soil_amendment_methods`; export const soilAmendmentPurposesUrl = `${URI}/soil_amendment_purposes`; export const soilAmendmentFertiliserTypesUrl = `${URI}/soil_amendment_fertiliser_types`; export const productUrl = `${URI}/product`; +export const farmAddonUrl = `${URI}/farm_addon`; export const url = URI; @@ -152,5 +153,6 @@ export default { soilAmendmentPurposesUrl, soilAmendmentFertiliserTypesUrl, productUrl, + farmAddonUrl, url, }; diff --git a/packages/webapp/src/assets/colors.scss b/packages/webapp/src/assets/colors.scss index c62c92fc1f..8cc411bd2e 100644 --- a/packages/webapp/src/assets/colors.scss +++ b/packages/webapp/src/assets/colors.scss @@ -76,6 +76,7 @@ --Colors-Primary-Primary-teal-50: #ebf5f4; --Colors-Primary-Primary-teal-100: #c0e1dd; + --Colors-Primary-Primary-teal-200: #a2d2cd; --Colors-Primary-Primary-teal-300: #78bdb6; --Colors-Primary-Primary-teal-400: #5db1a8; --Colors-Primary-Primary-teal-500: #359d92; @@ -109,6 +110,8 @@ --Btn-primary-hover: #e8a700; --Btn-primary-disabled: #e7ebf2; + --Colors-Backgrounds-Disabled-grey: #e7ebf2; + --Brand-Accents-colors-and-overlays-Accent-red: #eb6034; --Colors-Accent-Accent-yellow-50: #fff8e6; @@ -130,6 +133,7 @@ --Colors-Accent---singles-Purple-full: #8f26f0; --Form-focus: #89d1c7; + --Box-bg: #f5fafa; // Named in Figma but not in design system --Colors-Primary-green: #247360; diff --git a/packages/webapp/src/assets/images/partners/esci_logo.png b/packages/webapp/src/assets/images/partners/esci_logo.png new file mode 100644 index 0000000000..dbb33707c0 Binary files /dev/null and b/packages/webapp/src/assets/images/partners/esci_logo.png differ diff --git a/packages/webapp/src/components/Animals/FixedHeaderContainer/styles.module.scss b/packages/webapp/src/components/Animals/FixedHeaderContainer/styles.module.scss index 3ad3a048ac..52789756e8 100644 --- a/packages/webapp/src/components/Animals/FixedHeaderContainer/styles.module.scss +++ b/packages/webapp/src/components/Animals/FixedHeaderContainer/styles.module.scss @@ -44,7 +44,7 @@ .overflowStyle { display: flex; justify-content: center; - overflow-y: scroll; + overflow-y: auto; } .childrenWrapper { diff --git a/packages/webapp/src/components/FloatingContainer/index.tsx b/packages/webapp/src/components/FloatingContainer/index.tsx index fb35a1682b..c6f03a9119 100644 --- a/packages/webapp/src/components/FloatingContainer/index.tsx +++ b/packages/webapp/src/components/FloatingContainer/index.tsx @@ -14,10 +14,9 @@ */ import clsx from 'clsx'; +import { CSSLength } from '../../types'; import styles from './styles.module.scss'; -type CSSLength = `${number}px` | `${number}%` | `${number}vw` | `${number}vh` | 'auto'; - interface FloatingContainerProps { isCompactSideMenu: boolean; children: React.ReactNode; diff --git a/packages/webapp/src/components/Form/ContextForm/Loading.tsx b/packages/webapp/src/components/Form/ContextForm/Loading.tsx index e1eb3e95af..bb90c0a115 100644 --- a/packages/webapp/src/components/Form/ContextForm/Loading.tsx +++ b/packages/webapp/src/components/Form/ContextForm/Loading.tsx @@ -13,25 +13,52 @@ * GNU General Public License for more details, see . */ +import { CSSProperties } from 'react'; +import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; import Spinner from '../../Spinner'; +import FloatingContainer from '../../FloatingContainer'; +import type { CSSLength } from '../../../types'; import styles from './styles.module.scss'; interface LoadingProps { dataName?: string; + isCompactSideMenu: boolean; + verticalMargin?: CSSLength; + horizontalMargin?: CSSLength; + mobileMargin?: CSSLength; } -const Loading = ({ dataName = '' }: LoadingProps) => { +const Loading = ({ + dataName = '', + isCompactSideMenu, + verticalMargin = '36px', + horizontalMargin = '64px', + mobileMargin = '16px', +}: LoadingProps) => { const { t } = useTranslation(['translation', 'common']); + const style = { + '--vertical-margin': verticalMargin, + '--horizontal-margin': horizontalMargin, + '--mobile-margin': mobileMargin, + } as CSSProperties; return ( -
-
- + +
+
+ +
+
{t('common:LOADING')}
+
{t('common:FETCHING_YOUR_DATA', { dataName })}
-
{t('common:LOADING')}
-
{t('common:FETCHING_YOUR_DATA', { dataName })}
-
+ ); }; diff --git a/packages/webapp/src/components/Form/ContextForm/WithStepperProgressBar.tsx b/packages/webapp/src/components/Form/ContextForm/WithStepperProgressBar.tsx index 34f4088362..5a8d4f711d 100644 --- a/packages/webapp/src/components/Form/ContextForm/WithStepperProgressBar.tsx +++ b/packages/webapp/src/components/Form/ContextForm/WithStepperProgressBar.tsx @@ -36,7 +36,6 @@ interface WithStepperProgressBarProps { steps: { formContent: ReactNode; title: string; - onContinueAction?: (values: any) => Promise; dataName?: string; }[]; activeStepIndex: number; @@ -66,6 +65,9 @@ interface WithStepperProgressBarProps { showCancelFlow?: boolean; setShowCancelFlow?: React.Dispatch>; headerComponent?: ((props: HeaderProps) => JSX.Element) | null; + showPreviousButton?: boolean; + showLoading?: boolean; + onAfterSave?: () => void; } export const WithStepperProgressBar = ({ @@ -92,6 +94,9 @@ export const WithStepperProgressBar = ({ showCancelFlow, setShowCancelFlow, headerComponent = StepperProgressBar, + showPreviousButton = true, + showLoading, + onAfterSave, }: WithStepperProgressBarProps) => { const [transition, setTransition] = useState<{ unblock?: () => void; retry?: () => void }>({ unblock: undefined, @@ -99,6 +104,7 @@ export const WithStepperProgressBar = ({ }); const [isSaving, setIsSaving] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [isSaved, setIsSaved] = useState(false); const isSummaryPage = hasSummaryWithinForm && activeStepIndex === steps.length - 1; const isSingleStep = steps.length === 1; @@ -106,7 +112,7 @@ export const WithStepperProgressBar = ({ // Block the page transition // https://github.com/remix-run/history/blob/dev/docs/blocking-transitions.md useEffect(() => { - if (isSummaryPage || !isDirty) { + if (isSummaryPage || !isDirty || isSaved) { return; } const unblock = history.block((tx) => { @@ -114,7 +120,13 @@ export const WithStepperProgressBar = ({ }); return () => unblock(); - }, [isSummaryPage, isDirty, history]); + }, [isSummaryPage, isDirty, history, isSaved]); + + useEffect(() => { + if (isSaved && onAfterSave) { + onAfterSave(); + } + }, [isSaved, onAfterSave]); useEffect(() => { // Reset loading state whenever the step changes @@ -139,23 +151,16 @@ export const WithStepperProgressBar = ({ }; const onContinue = async () => { - const { onContinueAction } = steps[activeStepIndex]; - - if (onContinueAction) { + if (isFinalStep) { setIsLoading(true); + setIsSaving(true); try { - // Execute the custom action for the current step before proceeding to the next one - await onContinueAction(getValues()); + await handleSubmit((data: FieldValues) => onSave(data, onSuccess, setFormResultData))(); + setIsSaved(true); } catch (error) { - console.error(error); setIsLoading(false); - return; + console.error(error); } - } - - if (isFinalStep) { - setIsSaving(true); - await handleSubmit((data: FieldValues) => onSave(data, onSuccess, setFormResultData))(); setIsSaving(false); return; } @@ -179,8 +184,10 @@ export const WithStepperProgressBar = ({ setShowCancelFlow?.(false); }; - if (isLoading) { - return ; + if (showLoading && isLoading) { + return ( + + ); } return ( @@ -200,7 +207,7 @@ export const WithStepperProgressBar = ({ { - if (isSingleStep) { + if (isSingleStep && !headerComponent) { return <>{children}; } diff --git a/packages/webapp/src/components/Form/ContextForm/index.tsx b/packages/webapp/src/components/Form/ContextForm/index.tsx index 9b9cdae4fb..5e840a29b0 100644 --- a/packages/webapp/src/components/Form/ContextForm/index.tsx +++ b/packages/webapp/src/components/Form/ContextForm/index.tsx @@ -14,7 +14,7 @@ */ import { useState, useMemo } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; +import { FormProvider, useForm, ValidationMode } from 'react-hook-form'; import { WithPageTitle } from './WithPageTitle'; import { WithStepperProgressBar } from './WithStepperProgressBar'; @@ -35,6 +35,7 @@ interface ContextFormProps { variant?: Variant; isEditing?: boolean; setIsEditing?: React.Dispatch>; + formMode?: keyof ValidationMode; [key: string]: any; } @@ -45,6 +46,7 @@ export const ContextForm = ({ variant = Variant.PAGE_TITLE, isEditing = true, setIsEditing, + formMode = 'onBlur', ...props }: ContextFormProps) => { const [activeStepIndex, setActiveStepIndex] = useState(0); @@ -53,7 +55,7 @@ export const ContextForm = ({ const [showCancelFlow, setShowCancelFlow] = useState(false); const form = useForm({ - mode: 'onBlur', + mode: formMode, defaultValues: defaultFormValues, }); diff --git a/packages/webapp/src/components/Form/ContextForm/styles.module.scss b/packages/webapp/src/components/Form/ContextForm/styles.module.scss index 0acf48f762..20aca0459b 100644 --- a/packages/webapp/src/components/Form/ContextForm/styles.module.scss +++ b/packages/webapp/src/components/Form/ContextForm/styles.module.scss @@ -29,6 +29,29 @@ flex-direction: column; justify-content: center; align-items: center; + + padding: 16px; + background-color: var(--White); + border-radius: 8px; + box-shadow: 0px 0px 1px 0px #2b303a4d; + + height: calc(100vh - var(--global-navbar-height) - var(--vertical-margin) * 2); + + &.withCompactSideMenu { + width: calc(100vw - var(--global-compact-side-menu-width) - var(--horizontal-margin) * 2); + } + &.withExpandedSideMenu { + width: calc(100vw - var(--global-side-menu-width) - var(--horizontal-margin) * 2); + } + + @include xs-breakpoint { + &.withCompactSideMenu, + &.withExpandedSideMenu { + width: calc(100vw - var(--mobile-margin) * 2); + height: calc(100vh - var(--global-navbar-height) - var(--mobile-margin) * 2); + margin: var(--mobile-margin); + } + } } .loadingText { diff --git a/packages/webapp/src/components/PageTitle/v2/index.jsx b/packages/webapp/src/components/PageTitle/v2/index.jsx index 5f2accaeef..9c16feadb2 100644 --- a/packages/webapp/src/components/PageTitle/v2/index.jsx +++ b/packages/webapp/src/components/PageTitle/v2/index.jsx @@ -1,14 +1,15 @@ import { Title } from '../../Typography'; import React, { useState } from 'react'; +import clsx from 'clsx'; import styles from './styles.module.scss'; import { BsChevronLeft } from 'react-icons/bs'; import PropTypes from 'prop-types'; import { CancelButton } from '../CancelButton'; -function PageTitle({ title, onGoBack, onCancel, style, cancelModalTitle, label }) { +function PageTitle({ title, onGoBack, onCancel, style, cancelModalTitle, label, classNames = {} }) { const [showConfirmCancelModal, setShowConfirmCancelModal] = useState(false); return ( -
+
{onGoBack && (
); diff --git a/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/constants.ts b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/constants.ts new file mode 100644 index 0000000000..cca30b60da --- /dev/null +++ b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2025 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 EsciLogo from '../../../../../assets/images/partners/esci_logo.png'; + +export const PARTNERS = { + ESCI: { id: 1, name: 'Ensemble Scientific', url: 'www.esci.io', logoPath: EsciLogo }, +}; diff --git a/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/styles.module.scss b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/styles.module.scss index aa95ee5cc4..2064765215 100644 --- a/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/styles.module.scss +++ b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/styles.module.scss @@ -15,6 +15,8 @@ .formWrapper { height: 100%; - background-color: var(--White); - padding: 24px; +} + +.pageTitle { + padding: 24px 24px 0 24px; } diff --git a/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/types.ts b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/types.ts new file mode 100644 index 0000000000..e9c48d8766 --- /dev/null +++ b/packages/webapp/src/containers/LocationDetails/PointDetails/SensorDetail/v2/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2025 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 { FarmAddon } from '../../../../../store/api/types'; + +export const PARTNER = 'partner'; + +export const FarmAddonField = { + ORG_UUID: 'org_uuid', + PARTNER_ID: 'addon_partner_id', +} as const; + +export type AddSensorsFormFields = { + [PARTNER]: FarmAddon; +}; diff --git a/packages/webapp/src/containers/Map/index.jsx b/packages/webapp/src/containers/Map/index.jsx index 9b1cd76a00..a6c51ecaf2 100644 --- a/packages/webapp/src/containers/Map/index.jsx +++ b/packages/webapp/src/containers/Map/index.jsx @@ -4,25 +4,15 @@ import { useTranslation } from 'react-i18next'; import styles from './styles.module.scss'; import GoogleMap from 'google-map-react'; import { saveAs } from 'file-saver'; -import { - DEFAULT_ZOOM, - GMAPS_API_KEY, - isArea, - isLine, - locationEnum, - SENSOR_BULK_UPLOAD_SUCCESS, -} from './constants'; +import { DEFAULT_ZOOM, GMAPS_API_KEY, isArea, isLine, locationEnum } from './constants'; import { useDispatch, useSelector } from 'react-redux'; import { measurementSelector, userFarmSelector } from '../userFarmSlice'; import html2canvas from 'html2canvas'; import { sendMapToEmail, setSpotlightToShown, - bulkUploadSensorsInfoFile, getSensorReadings, getAllSensorReadingTypes, - resetBulkUploadSensorsInfoFile, - resetShowTransitionModalState, } from './saga'; import { canShowSuccessHeader, @@ -39,8 +29,6 @@ import DrawAreaModal from '../../components/Map/Modals/DrawArea'; import DrawLineModal from '../../components/Map/Modals/DrawLine'; import AdjustAreaModal from '../../components/Map/Modals/AdjustArea'; import AdjustLineModal from '../../components/Map/Modals/AdjustLine'; -import BulkSensorUploadModal from '../../components/Map/Modals/BulkSensorUploadModal'; -import BulkUploadTransitionModal from '../../components/Modals/BulkUploadTransitionModal'; import CustomZoom from '../../components/Map/CustomZoom'; import CustomCompass from '../../components/Map/CustomCompass'; import DrawingManager from '../../components/Map/DrawingManager'; @@ -65,10 +53,6 @@ import { setIsRedrawing, hookFormPersistIsRedrawingSelector, } from '../hooks/useHookFormPersist/hookFormPersistSlice'; -import { - bulkSensorsUploadSliceSelector, - bulkSensorsUploadReInit, -} from '../../containers/bulkSensorUploadSlice'; import LocationSelectionModal from './LocationSelectionModal'; import { useMaxZoom } from './useMaxZoom'; import { @@ -77,6 +61,7 @@ import { setMapAddDrawerShow, } from './mapAddDrawerSlice'; import clsx from 'clsx'; +import { POST_SENSOR_URL } from '../../util/siteMapConstants'; export default function Map({ history, isCompactSideMenu }) { const { farm_name, grid_points, is_admin, farm_id } = useSelector(userFarmSelector); @@ -88,7 +73,6 @@ export default function Map({ history, isCompactSideMenu }) { const dispatch = useDispatch(); const system = useSelector(measurementSelector); const overlayData = useSelector(hookFormPersistSelector); - const bulkSensorsUploadResponse = useSelector(bulkSensorsUploadSliceSelector); const [gMap, setGMap] = useState(null); const [gMaps, setGMaps] = useState(null); const [gMapBounds, setGMapBounds] = useState(null); @@ -130,23 +114,6 @@ export default function Map({ history, isCompactSideMenu }) { }; }, []); - useEffect(() => { - if (bulkSensorsUploadResponse?.isBulkUploadSuccessful && gMaps && gMap) { - getMaxZoom(gMaps, gMap); - setShowBulkSensorUploadModal(false); - } - }, [bulkSensorsUploadResponse?.isBulkUploadSuccessful, gMaps, gMap]); - - useEffect(() => { - setShowBulkSensorUploadModal(false); - }, [bulkSensorsUploadResponse?.showTransitionModal]); - - useEffect(() => { - if (history.location.state?.notification_type === SENSOR_BULK_UPLOAD_SUCCESS) { - dispatch(setMapFilterShowAll(farm_id)); - } - }, []); - const [ drawingState, { @@ -183,7 +150,6 @@ export default function Map({ history, isCompactSideMenu }) { const [showExportModal, setShowExportModal] = useState(false); const [showDrawAreaSpotlightModal, setShowDrawAreaSpotlightModal] = useState(false); const [showDrawLineSpotlightModal, setShowDrawLineSpotlightModal] = useState(false); - const [showBulkSensorUploadModal, setShowBulkSensorUploadModal] = useState(false); const getMapOptions = (maps) => { return { @@ -389,8 +355,7 @@ export default function Map({ history, isCompactSideMenu }) { setShowDrawLineSpotlightModal(true); } else if (locationType === locationEnum.sensor) { dispatch(showAddDrawer ? setMapAddDrawerHide(farm_id) : setMapAddDrawerShow(farm_id)); - setShowBulkSensorUploadModal(true); - dispatch(resetBulkUploadSensorsInfoFile()); + history.push(POST_SENSOR_URL); return; } isLineWithWidth(locationType) && dispatch(upsertFormData(initialLineData[locationType])); @@ -408,9 +373,6 @@ export default function Map({ history, isCompactSideMenu }) { const handleCloseSuccessHeader = () => { dispatch(canShowSuccessHeader(false)); setShowSuccessHeader(false); - if (bulkSensorsUploadResponse?.isBulkUploadSuccessful) { - dispatch(bulkSensorsUploadReInit()); - } }; useEffect(() => { @@ -462,11 +424,6 @@ export default function Map({ history, isCompactSideMenu }) { return lineTypesWithWidth.includes(type); }; - const dismissBulkSensorsUploadModal = () => { - setShowBulkSensorUploadModal(false); - dispatch(setMapAddDrawerShow(farm_id)); - }; - const { showAdjustAreaSpotlightModal, showAdjustLineSpotlightModal, @@ -615,22 +572,6 @@ export default function Map({ history, isCompactSideMenu }) { }} /> )} - {showBulkSensorUploadModal && ( - { - const payload = { file }; - dispatch(bulkUploadSensorsInfoFile(payload)); - }} - /> - )} - {(bulkSensorsUploadResponse?.showTransitionModal ?? false) && ( - { - dispatch(resetShowTransitionModalState()); - }} - /> - )}
); diff --git a/packages/webapp/src/routes/index.jsx b/packages/webapp/src/routes/index.jsx index 7df29c0c11..93ac5e9741 100644 --- a/packages/webapp/src/routes/index.jsx +++ b/packages/webapp/src/routes/index.jsx @@ -29,6 +29,8 @@ import { chooseFarmFlowSelector } from '../containers/ChooseFarm/chooseFarmFlowS import useScrollToTop from '../containers/hooks/useScrollToTop'; import { useReduxSnackbar } from '../containers/Snackbar/useReduxSnackbar'; +import { POST_SENSOR_URL } from '../util/siteMapConstants'; + //dynamic imports const Home = React.lazy(() => import('../containers/Home')); const Account = React.lazy(() => import('../containers/Profile/Account')); @@ -54,85 +56,87 @@ const Prices = React.lazy(() => import('../containers/Insights/Prices')); const ExpiredTokenScreen = React.lazy(() => import('../containers/ExpiredTokenScreen')); const Map = React.lazy(() => import('../containers/Map')); const MapVideo = React.lazy(() => import('../components/Map/Videos')); -const PostFarmSiteBoundaryForm = React.lazy(() => - import( - '../containers/LocationDetails/AreaDetails/FarmSiteBoundaryDetailForm/PostFarmSiteBoundary' - ), +const PostFarmSiteBoundaryForm = React.lazy( + () => + import( + '../containers/LocationDetails/AreaDetails/FarmSiteBoundaryDetailForm/PostFarmSiteBoundary' + ), ); const FarmSiteBoundaryDetails = React.lazy(() => import('./FarmSiteBoundaryDetailsRoutes')); -const PostFieldForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/FieldDetailForm/PostField'), +const PostFieldForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/FieldDetailForm/PostField'), ); const FieldDetails = React.lazy(() => import('./FieldDetailsRoutes')); -const PostGardenForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/GardenDetailForm/PostGarden'), +const PostGardenForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/GardenDetailForm/PostGarden'), ); const GardenDetails = React.lazy(() => import('./GardenDetailsRoutes')); -const PostGateForm = React.lazy(() => - import('../containers/LocationDetails/PointDetails/GateDetailForm/PostGate'), +const PostGateForm = React.lazy( + () => import('../containers/LocationDetails/PointDetails/GateDetailForm/PostGate'), ); const GateDetails = React.lazy(() => import('./GateDetailsRoutes')); -const PostWaterValveForm = React.lazy(() => - import('../containers/LocationDetails/PointDetails/WaterValveDetailForm/PostWaterValve'), +const PostWaterValveForm = React.lazy( + () => import('../containers/LocationDetails/PointDetails/WaterValveDetailForm/PostWaterValve'), ); const WaterValveDetails = React.lazy(() => import('./WaterValveDetailsRoutes')); -const EditSensor = React.lazy(() => - import('../containers/LocationDetails/PointDetails/SensorDetail/EditSensor'), +const EditSensor = React.lazy( + () => import('../containers/LocationDetails/PointDetails/SensorDetail/EditSensor'), ); -const PostBarnForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/BarnDetailForm/PostBarn'), +const PostBarnForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/BarnDetailForm/PostBarn'), ); const BarnDetails = React.lazy(() => import('./BarnDetailsRoutes')); -const PostNaturalAreaForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/NaturalAreaDetailForm/PostNaturalArea'), +const PostNaturalAreaForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/NaturalAreaDetailForm/PostNaturalArea'), ); const NaturalAreaDetails = React.lazy(() => import('./NaturalAreaDetailsRoutes')); -const PostSurfaceWaterForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/SurfaceWaterDetailForm/PostSurfaceWater'), +const PostSurfaceWaterForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/SurfaceWaterDetailForm/PostSurfaceWater'), ); const SurfaceWaterDetails = React.lazy(() => import('./SurfaceWaterDetailsRoutes')); -const PostResidenceForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/ResidenceDetailForm/PostResidence'), +const PostResidenceForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/ResidenceDetailForm/PostResidence'), ); const ResidenceDetails = React.lazy(() => import('./ResidenceDetailsRoutes')); -const PostCeremonialForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/CeremonialAreaDetailForm/PostCeremonialArea'), +const PostCeremonialForm = React.lazy( + () => + import('../containers/LocationDetails/AreaDetails/CeremonialAreaDetailForm/PostCeremonialArea'), ); const CeremonialAreaDetails = React.lazy(() => import('./CeremonialAreaDetailsRoutes')); -const PostGreenhouseForm = React.lazy(() => - import('../containers/LocationDetails/AreaDetails/GreenhouseDetailForm/PostGreenhouse'), +const PostGreenhouseForm = React.lazy( + () => import('../containers/LocationDetails/AreaDetails/GreenhouseDetailForm/PostGreenhouse'), ); const GreenhouseDetails = React.lazy(() => import('./GreenhouseDetailsRoutes')); const CropManagement = React.lazy(() => import('../containers/Crop/CropManagement')); const CropDetail = React.lazy(() => import('../containers/Crop/CropDetail/index')); -const PostFenceForm = React.lazy(() => - import('../containers/LocationDetails/LineDetails/FenceDetailForm/PostFence'), +const PostFenceForm = React.lazy( + () => import('../containers/LocationDetails/LineDetails/FenceDetailForm/PostFence'), ); const FenceDetails = React.lazy(() => import('./FenceDetailsRoutes')); -const PostBufferZoneForm = React.lazy(() => - import('../containers/LocationDetails/LineDetails/BufferZoneDetailForm/PostBufferZone'), +const PostBufferZoneForm = React.lazy( + () => import('../containers/LocationDetails/LineDetails/BufferZoneDetailForm/PostBufferZone'), ); const BufferZoneDetails = React.lazy(() => import('./BufferZoneDetailsRoutes')); -const PostWatercourseForm = React.lazy(() => - import('../containers/LocationDetails/LineDetails/WatercourseDetailForm/PostWatercourse'), +const PostWatercourseForm = React.lazy( + () => import('../containers/LocationDetails/LineDetails/WatercourseDetailForm/PostWatercourse'), ); const WatercourseDetails = React.lazy(() => import('./WatercourseDetailsRoutes')); -const PostSensorForm = React.lazy(() => - import('../containers/LocationDetails/PointDetails/SensorDetail/v2/PostSensor'), +const PostSensorForm = React.lazy( + () => import('../containers/LocationDetails/PointDetails/SensorDetail/v2/PostSensor'), ); const SensorDetails = React.lazy(() => import('./SensorDetailsRoutes')); @@ -142,34 +146,34 @@ const AddCrop = React.lazy(() => import('../containers/AddCropVariety/AddCropVar const EditCrop = React.lazy(() => import('../containers/EditCropVariety')); const ComplianceInfo = React.lazy(() => import('../containers/AddCropVariety/ComplianceInfo')); const AddNewCrop = React.lazy(() => import('../containers/AddNewCrop')); -const PlantingLocation = React.lazy(() => - import('../containers/Crop/AddManagementPlan/PlantingLocation'), +const PlantingLocation = React.lazy( + () => import('../containers/Crop/AddManagementPlan/PlantingLocation'), ); const Transplant = React.lazy(() => import('../containers/Crop/AddManagementPlan/Transplant')); const PlantingDate = React.lazy(() => import('../containers/Crop/AddManagementPlan/PlantingDate')); -const PlantingMethod = React.lazy(() => - import('../containers/Crop/AddManagementPlan/PlantingMethod'), +const PlantingMethod = React.lazy( + () => import('../containers/Crop/AddManagementPlan/PlantingMethod'), ); -const PlantInContainer = React.lazy(() => - import('../containers/Crop/AddManagementPlan/PlantInContainer'), +const PlantInContainer = React.lazy( + () => import('../containers/Crop/AddManagementPlan/PlantInContainer'), ); -const PlantBroadcast = React.lazy(() => - import('../containers/Crop/AddManagementPlan/BroadcastPlan'), +const PlantBroadcast = React.lazy( + () => import('../containers/Crop/AddManagementPlan/BroadcastPlan'), ); const BedPlan = React.lazy(() => import('../containers/Crop/AddManagementPlan/BedPlan/BedPlan')); -const BedPlanGuidance = React.lazy(() => - import('../containers/Crop/AddManagementPlan/BedPlan/BedPlanGuidance'), +const BedPlanGuidance = React.lazy( + () => import('../containers/Crop/AddManagementPlan/BedPlan/BedPlanGuidance'), ); -const ManagementPlanName = React.lazy(() => - import('../containers/Crop/AddManagementPlan/ManagementPlanName'), +const ManagementPlanName = React.lazy( + () => import('../containers/Crop/AddManagementPlan/ManagementPlanName'), ); const RowMethod = React.lazy(() => import('../containers/Crop/AddManagementPlan/RowMethod')); -const RowMethodGuidance = React.lazy(() => - import('../containers/Crop/AddManagementPlan/RowMethod/RowGuidance'), +const RowMethodGuidance = React.lazy( + () => import('../containers/Crop/AddManagementPlan/RowMethod/RowGuidance'), ); -const PlantedAlready = React.lazy(() => - import('../containers/Crop/AddManagementPlan/PlantedAlready'), +const PlantedAlready = React.lazy( + () => import('../containers/Crop/AddManagementPlan/PlantedAlready'), ); const Documents = React.lazy(() => import('../containers/Documents')); @@ -178,60 +182,63 @@ const EditDocument = React.lazy(() => import('../containers/Documents/Edit')); const AddDocument = React.lazy(() => import('../containers/Documents/Add')); const MainDocument = React.lazy(() => import('../containers/Documents/Main')); -const CertificationReportingPeriod = React.lazy(() => - import('../containers/Certifications/ReportingPeriod'), +const CertificationReportingPeriod = React.lazy( + () => import('../containers/Certifications/ReportingPeriod'), ); const CertificationSurvey = React.lazy(() => import('../containers/Certifications/Survey')); -const InterestedOrganic = React.lazy(() => - import('../containers/OrganicCertifierSurvey/InterestedOrganic/UpdateInterestedOrganic'), +const InterestedOrganic = React.lazy( + () => import('../containers/OrganicCertifierSurvey/InterestedOrganic/UpdateInterestedOrganic'), ); -const CertificationSelection = React.lazy(() => - import( - '../containers/OrganicCertifierSurvey/CertificationSelection/UpdateCertificationSelection' - ), +const CertificationSelection = React.lazy( + () => + import( + '../containers/OrganicCertifierSurvey/CertificationSelection/UpdateCertificationSelection' + ), ); -const CertifierSelectionMenu = React.lazy(() => - import( - '../containers/OrganicCertifierSurvey/CertifierSelectionMenu/UpdateCertifierSelectionMenu' - ), +const CertifierSelectionMenu = React.lazy( + () => + import( + '../containers/OrganicCertifierSurvey/CertifierSelectionMenu/UpdateCertifierSelectionMenu' + ), ); -const SetCertificationSummary = React.lazy(() => - import( - '../containers/OrganicCertifierSurvey/SetCertificationSummary/UpdateSetCertificationSummary' - ), +const SetCertificationSummary = React.lazy( + () => + import( + '../containers/OrganicCertifierSurvey/SetCertificationSummary/UpdateSetCertificationSummary' + ), ); -const RequestCertifier = React.lazy(() => - import('../containers/OrganicCertifierSurvey/RequestCertifier/UpdateRequestCertifier'), +const RequestCertifier = React.lazy( + () => import('../containers/OrganicCertifierSurvey/RequestCertifier/UpdateRequestCertifier'), ); -const ViewCertification = React.lazy(() => - import('../containers/OrganicCertifierSurvey/ViewCertification/ViewCertification'), +const ViewCertification = React.lazy( + () => import('../containers/OrganicCertifierSurvey/ViewCertification/ViewCertification'), ); const RenderSurvey = React.lazy(() => import('../containers/RenderSurvey/RenderSurvey')); const ExportDownload = React.lazy(() => import('../containers/ExportDownload')); -const ManagementTasks = React.lazy(() => - import('../containers/Crop/ManagementDetail/ManagementTasks'), +const ManagementTasks = React.lazy( + () => import('../containers/Crop/ManagementDetail/ManagementTasks'), ); -const ManagementDetails = React.lazy(() => - import('../containers/Crop/ManagementDetail/ManagementDetails'), +const ManagementDetails = React.lazy( + () => import('../containers/Crop/ManagementDetail/ManagementDetails'), ); -const EditManagementDetails = React.lazy(() => - import('../containers/Crop/ManagementDetail/EditManagementDetails'), +const EditManagementDetails = React.lazy( + () => import('../containers/Crop/ManagementDetail/EditManagementDetails'), ); -const CompleteManagementPlan = React.lazy(() => - import('../containers/Crop/CompleteManagementPlan/CompleteManagementPlan'), +const CompleteManagementPlan = React.lazy( + () => import('../containers/Crop/CompleteManagementPlan/CompleteManagementPlan'), ); -const AbandonManagementPlan = React.lazy(() => - import('../containers/Crop/CompleteManagementPlan/AbandonManagementPlan'), +const AbandonManagementPlan = React.lazy( + () => import('../containers/Crop/CompleteManagementPlan/AbandonManagementPlan'), ); const RepeatCropPlan = React.lazy(() => import('../containers/Crop/RepeatCropPlan')); -const RepeatCropPlanConfirmation = React.lazy(() => - import('../containers/Crop/RepeatCropPlan/Confirmation'), +const RepeatCropPlanConfirmation = React.lazy( + () => import('../containers/Crop/RepeatCropPlan/Confirmation'), ); const TaskAssignment = React.lazy(() => import('../containers/Task/TaskAssignment')); @@ -245,41 +252,41 @@ const Tasks = React.lazy(() => import('../containers/Task')); const ManageCustomTasks = React.lazy(() => import('../containers/Task/ManageCustomTasks')); const AddCustomTask = React.lazy(() => import('../containers/Task/AddCustomTask')); const TaskComplete = React.lazy(() => import('../containers/Task/TaskComplete')); -const HarvestCompleteQuantity = React.lazy(() => - import('../containers/Task/TaskComplete/HarvestComplete/Quantity'), +const HarvestCompleteQuantity = React.lazy( + () => import('../containers/Task/TaskComplete/HarvestComplete/Quantity'), ); -const HarvestUses = React.lazy(() => - import('../containers/Task/TaskComplete/HarvestComplete/HarvestUses'), +const HarvestUses = React.lazy( + () => import('../containers/Task/TaskComplete/HarvestComplete/HarvestUses'), ); const TaskCompleteStepOne = React.lazy(() => import('../containers/Task/TaskComplete/StepOne')); const TaskReadOnly = React.lazy(() => import('../containers/Task/TaskReadOnly')); const EditCustomTask = React.lazy(() => import('../containers/Task/EditCustomTask')); const TaskAbandon = React.lazy(() => import('../containers/Task/TaskAbandon')); const EditCustomTaskUpdate = React.lazy(() => import('../containers/Task/EditCustomTaskUpdate')); -const TaskTransplantMethod = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskTransplantMethod'), +const TaskTransplantMethod = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskTransplantMethod'), ); -const TaskBedMethod = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskBedMethod'), +const TaskBedMethod = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskBedMethod'), ); -const TaskBedGuidance = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskBedGuidance'), +const TaskBedGuidance = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskBedGuidance'), ); -const TaskRowMethod = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskRowMethod'), +const TaskRowMethod = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskRowMethod'), ); -const TaskRowGuidance = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskRowGuidance'), +const TaskRowGuidance = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskRowGuidance'), ); -const TaskContainerMethod = React.lazy(() => - import('../containers/Task/TaskTransplantMethod/TaskContainerMethod'), +const TaskContainerMethod = React.lazy( + () => import('../containers/Task/TaskTransplantMethod/TaskContainerMethod'), ); const Notification = React.lazy(() => import('../containers/Notification')); -const NotificationReadOnly = React.lazy(() => - import('../containers/Notification/NotificationReadOnly'), +const NotificationReadOnly = React.lazy( + () => import('../containers/Notification/NotificationReadOnly'), ); -const UnknownRecord = React.lazy(() => - import('../containers/ErrorHandler/UnknownRecord/UnknownRecord'), +const UnknownRecord = React.lazy( + () => import('../containers/ErrorHandler/UnknownRecord/UnknownRecord'), ); const Routes = ({ isCompactSideMenu, isFeedbackSurveyOpen, setFeedbackSurveyOpen }) => { @@ -536,7 +543,13 @@ const Routes = ({ isCompactSideMenu, isFeedbackSurveyOpen, setFeedbackSurveyOpen - + ( + + )} + /> @@ -821,7 +834,13 @@ const Routes = ({ isCompactSideMenu, isFeedbackSurveyOpen, setFeedbackSurveyOpen - + ( + + )} + /> diff --git a/packages/webapp/src/store/api/apiSlice.ts b/packages/webapp/src/store/api/apiSlice.ts index 6dcf2ffda0..70429b8c54 100644 --- a/packages/webapp/src/store/api/apiSlice.ts +++ b/packages/webapp/src/store/api/apiSlice.ts @@ -35,6 +35,7 @@ import { url, animalMovementPurposesUrl, sensorUrl, + farmAddonUrl, } from '../../apiConfig'; import type { Animal, @@ -55,6 +56,7 @@ import type { AnimalUse, AnimalMovementPurpose, SensorData, + FarmAddon, } from './types'; export const api = createApi({ @@ -91,6 +93,7 @@ export const api = createApi({ 'SoilAmendmentFertiliserTypes', 'SoilAmendmentProduct', 'Sensors', + 'FarmAddon', ], endpoints: (build) => ({ // redux-toolkit.js.org/rtk-query/usage-with-typescript#typing-query-and-mutation-endpoints @@ -246,6 +249,14 @@ export const api = createApi({ keepUnusedDataFor: 60 * 60 * 24 * 365, // 1 year providesTags: ['Sensors'], }), + addFarmAddon: build.mutation({ + query: (body) => ({ + url: `${farmAddonUrl}`, + method: 'POST', + body, + }), + invalidatesTags: ['FarmAddon'], + }), }), }); @@ -277,4 +288,6 @@ export const { useAddSoilAmendmentProductMutation, useUpdateSoilAmendmentProductMutation, useGetSensorsQuery, + useLazyGetSensorsQuery, + useAddFarmAddonMutation, } = api; diff --git a/packages/webapp/src/store/api/types/index.ts b/packages/webapp/src/store/api/types/index.ts index 577994987b..fb40807e53 100644 --- a/packages/webapp/src/store/api/types/index.ts +++ b/packages/webapp/src/store/api/types/index.ts @@ -270,3 +270,8 @@ export interface SensorData { sensors: Sensor[]; sensor_arrays: SensorArray[]; } + +export interface FarmAddon { + addon_partner_id: number; + org_uuid: string; +} diff --git a/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx b/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx index 34739f03c0..787d0aa148 100644 --- a/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx +++ b/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx @@ -114,21 +114,48 @@ const asyncFunc = async (status: 'success' | 'fail') => { }); }; -export const StepperFormWithCustomActionOnContinue: Story = { +export const StepperSuccessFormWithLoading: Story = { args: { ...stepperFormCommonProps, hasSummaryWithinForm: true, + showLoading: true, + onSave: async (data: any, onSuccess: () => void) => { + await asyncFunc('success'); + onSuccess(); + }, + getSteps: () => [ + { + title: 'Page 1', + FormContent: () =>
Page 1
, + dataName: 'sensor', + }, + { + title: 'Page 2', + FormContent: () =>
Page 2
, + }, + { + title: 'Done', + FormContent: () =>
Summary
, + }, + ], + }, +}; + +export const StepperFailedFormWithLoading: Story = { + args: { + ...stepperFormCommonProps, + hasSummaryWithinForm: true, + showLoading: true, + onSave: () => asyncFunc('fail'), getSteps: () => [ { title: 'Page 1', FormContent: () =>
Page 1
, - onContinueAction: () => asyncFunc('success'), dataName: 'sensor', }, { title: 'Page 2', FormContent: () =>
Page 2
, - onContinueAction: () => asyncFunc('fail'), }, { title: 'Done', diff --git a/packages/webapp/src/types/index.ts b/packages/webapp/src/types/index.ts index 318ee7f60b..eaa1bebc1e 100644 --- a/packages/webapp/src/types/index.ts +++ b/packages/webapp/src/types/index.ts @@ -37,3 +37,5 @@ export interface Location { [key: string]: any; } + +export type CSSLength = `${number}px` | `${number}%` | `${number}vw` | `${number}vh` | 'auto'; diff --git a/packages/webapp/src/util/siteMapConstants.ts b/packages/webapp/src/util/siteMapConstants.ts index 20dd5d424e..eb6b1ca765 100644 --- a/packages/webapp/src/util/siteMapConstants.ts +++ b/packages/webapp/src/util/siteMapConstants.ts @@ -92,3 +92,6 @@ export const createCompleteTaskUrl = (id: string | number, hasAnimals: boolean): // Maps export const MAP_URL = '/map'; export const POST_SENSOR_URL = '/create_location/sensor'; + +// Sensors +export const SENSORS = '/sensors';