;
- }>,
- ) => {
- state.currentPageNo = action.payload.currentPageNo;
- state.surveyDataInProgress = { ...state.surveyDataInProgress, ...action.payload.surveyData };
- },
- reopenSurvey: (state) => {
- state.currentPageNo = 0;
- },
- clearSurvey: () => initialState,
- },
-});
-
-export const { saveSurveyProgress, reopenSurvey, clearSurvey } = tapeSurveySlice.actions;
-export default tapeSurveySlice.reducer;
-
-// Selectors
-export const tapeSurveySelector = (state: any) =>
- // Re-evaluate placement in farmStateReducer when adding backend
- state.farmStateReducer[tapeSurveySlice.name] || initialState;
-
-export const tapeSurveyStatusSelector = createSelector([tapeSurveySelector], (tapeSurvey) => ({
- inProgress: Object.keys(tapeSurvey.surveyDataInProgress).length > 0,
-}));
diff --git a/packages/webapp/src/containers/Insights/index.jsx b/packages/webapp/src/containers/Insights/index.jsx
index f131aa5f03..efd42fd5ad 100644
--- a/packages/webapp/src/containers/Insights/index.jsx
+++ b/packages/webapp/src/containers/Insights/index.jsx
@@ -19,7 +19,6 @@ import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import styles from './styles.module.scss';
// images
-import tape_survey from '../../assets/images/insights/tape_survey.svg';
import soil_om from '../../assets/images/insights/soil_om.svg';
import labour_happiness from '../../assets/images/insights/labour_happiness.svg';
import biodiversity from '../../assets/images/insights/biodiversity.svg';
@@ -34,20 +33,18 @@ import {
pricesSelector,
soilOMSelector,
} from './selectors';
-import { tapeSurveyStatusSelector } from './TapeSurvey/tapeSurveySlice';
import InfoBoxComponent from '../../components/InfoBoxComponent';
import { BsChevronRight } from 'react-icons/bs';
import { isAdminSelector, userFarmSelector } from '../userFarmSlice';
import { Semibold, Text, Title } from '../../components/Typography';
import { useIsOffline } from '../hooks/useOfflineDetector/useIsOffline';
-import { useGetTapeSurveyQuery } from '../../store/api/tapeSurveyApi';
-import { getSurveyVersion } from './TapeSurvey/getSurveyVersion';
+import { SURVEY_INFO, getAvailableSurveyIds } from './Survey/surveyConfig';
+import SurveyInsightTile from './Survey/SurveyInsightTile';
const Insights = () => {
const history = useHistory();
const farm = useSelector(userFarmSelector);
- const tapeStatus = useSelector(tapeSurveyStatusSelector);
const pricesDistance = useSelector(pricesDistanceSelector);
const soilOMData = useSelector(soilOMSelector);
const labourHappinessData = useSelector(labourHappinessSelector);
@@ -59,22 +56,7 @@ const Insights = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
- const surveyVersion = getSurveyVersion(farm?.country_code);
- const {
- data: tapeSurvey,
- isError: isTapeSurveyError,
- isFetching: isTapeSurveyFetching,
- } = useGetTapeSurveyQuery();
-
- const isTapeSurveyCompleted = !isTapeSurveyError && !!tapeSurvey?.id;
-
const items = [
- {
- label: t('INSIGHTS.TAPE.TITLE'),
- image: tape_survey,
- route: isTapeSurveyCompleted ? 'tape/results' : 'tape',
- data_point: 'TAPE',
- },
{
label: t('INSIGHTS.SOIL_OM.TITLE'),
image: soil_om,
@@ -143,17 +125,7 @@ const Insights = () => {
};
const insightData = useMemo(() => {
- let tapeCurrentData = t('INSIGHTS.TAPE.NOT_FILLED');
- if (isTapeSurveyFetching) {
- tapeCurrentData = t('common:LOADING');
- } else if (tapeStatus.inProgress) {
- tapeCurrentData = t('INSIGHTS.TAPE.IN_PROGRESS');
- } else if (isTapeSurveyCompleted) {
- tapeCurrentData = t('INSIGHTS.TAPE.COMPLETED');
- }
-
const insightData = {};
- insightData['TAPE'] = tapeCurrentData;
insightData['SoilOM'] = (soilOMData.preview ?? '0') + '%';
insightData['LabourHappiness'] = labourHappinessData.preview
? labourHappinessData.preview + '/5'
@@ -163,28 +135,30 @@ const Insights = () => {
? t('INSIGHTS.PRICES.PERCENT_OF_MARKET', { percentage: pricesData.preview })
: t('INSIGHTS.UNAVAILABLE');
return insightData;
- }, [
- tapeStatus?.inProgress,
- isTapeSurveyFetching,
- isTapeSurveyCompleted,
- soilOMData,
- labourHappinessData,
- biodiversityData,
- pricesData,
- ]);
+ }, [soilOMData, labourHappinessData, biodiversityData, pricesData]);
+
+ // Surveys are shown only to admins and only when online. getAvailableSurveyIds gates the list to
+ // surveys available in the farm's country (see SURVEY_INFO).
+ const surveyTiles = useMemo(() => {
+ if (isOffline || !isAdmin) {
+ return [];
+ }
+ return getAvailableSurveyIds(farm?.country_code).map((surveyId, index) => (
+
+ ));
+ }, [farm?.country_code, isOffline, isAdmin]);
const renderedItems = useMemo(() => {
- return (
- insightData &&
- items
- .filter(
- (item) => !((isOffline || !isAdmin || !surveyVersion) && item.data_point === 'TAPE'),
- )
- .map((item, index) => {
- return renderItem(item, index, insightData[item.data_point]);
- })
+ const otherTiles = items.map((item, index) =>
+ renderItem(item, surveyTiles.length + index, insightData[item.data_point]),
);
- }, [insightData, isOffline]);
+ return [...surveyTiles, ...otherTiles];
+ }, [insightData, isOffline, surveyTiles]);
return (
diff --git a/packages/webapp/src/containers/saga.js b/packages/webapp/src/containers/saga.js
index 05b20e55cb..b6d7e24d0b 100644
--- a/packages/webapp/src/containers/saga.js
+++ b/packages/webapp/src/containers/saga.js
@@ -101,6 +101,7 @@ import {
userFarmsByFarmSelector,
} from './userFarmSlice';
import { logUserInfoSuccess, userLogReducerSelector } from './userLogSlice';
+import { resetFarmStateReducer } from '../store/actionTypes';
import { api, invalidateTags } from '../store/api/apiSlice';
import { locationApi } from '../store/api/locationApi';
import { FarmLibraryTags, FarmTags } from '../store/api/apiTags';
@@ -555,6 +556,7 @@ export function* fetchAllSaga() {
}
export function* clearOldFarmStateSaga() {
+ yield put(resetFarmStateReducer());
yield put(resetTasks());
yield put(resetDateRange());
releaseFarmScopedQuerySubscriptions();
diff --git a/packages/webapp/src/locales/km/rcYearCalendar.json b/packages/webapp/src/locales/km/rcYearCalendar.json
new file mode 100644
index 0000000000..d72a5da75a
--- /dev/null
+++ b/packages/webapp/src/locales/km/rcYearCalendar.json
@@ -0,0 +1,59 @@
+{
+ "days": [
+ "ថ្ងៃអាទិត្យ",
+ "ថ្ងៃច័ន្ទ",
+ "ថ្ងៃអង្គារ",
+ "ថ្ងៃពុធ",
+ "ថ្ងៃព្រហស្បតិ៍",
+ "ថ្ងៃសុក្រ",
+ "ថ្ងៃសោរ៍"
+ ],
+ "daysShort": [
+ "អាទិត្យ",
+ "ច័ន្ទ",
+ "អង្គារ",
+ "ពុធ",
+ "ព្រហស្បតិ៍",
+ "សុក្រ",
+ "សៅរ៍"
+ ],
+ "daysMin": [
+ "អា",
+ "ច",
+ "អ",
+ "ពុ",
+ "ព្រ",
+ "សុ",
+ "ស"
+ ],
+ "months": [
+ "ខែមករា",
+ "ខែកុម្ភៈ",
+ "ខែមីនា",
+ "ខែមេសា",
+ "ខែឧសភា",
+ "ខែមិថុនា",
+ "ខែកក្កដា",
+ "ខែសីហា",
+ "ខែកញ្ញា",
+ "ខែតុលា",
+ "ខែវិច្ឆិកា",
+ "ខែធ្នូ"
+ ],
+ "monthsShort": [
+ "មករា",
+ "កុម្ភៈ",
+ "មីនា",
+ "មេសា",
+ "ឧសភា",
+ "មិថុនា",
+ "កក្កដា",
+ "សីហា",
+ "កញ្ញា",
+ "តុលា",
+ "វិច្ឆិកា",
+ "ធ្នូ"
+ ],
+ "weekShort": "សប្តាហ៍",
+ "weekStart": "០"
+}
diff --git a/packages/webapp/src/locales/km/rrule.json b/packages/webapp/src/locales/km/rrule.json
new file mode 100644
index 0000000000..75131c3e2c
--- /dev/null
+++ b/packages/webapp/src/locales/km/rrule.json
@@ -0,0 +1,56 @@
+{
+ "getText": {
+ "every": "រៀងរាល់",
+ "until": "រហូតដល់",
+ "day": "ថ្ងៃ",
+ "days": "ថ្ងៃ",
+ "week": "សប្តាហ៍",
+ "weeks": "សប្តាហ៍",
+ "on": "លើ",
+ "in": "ក្នុង",
+ "on the": "នៅលើ",
+ "for": "សម្រាប់",
+ "and": "និង",
+ "or": "ឬ",
+ "at": "នៅ",
+ "last": "ចុងក្រោយ",
+ "st": "ទីមួយ",
+ "nd": "ទីពីរ",
+ "rd": "ទីបី",
+ "th": "ទី",
+ "(~ approximate)": "(~ ប្រហាក់ប្រហែល)",
+ "times": "ចំនួនដង",
+ "time": "ពេល",
+ "minutes": "នាទី",
+ "hours": "ម៉ោង",
+ "weekdays": "ថ្ងៃធ្វើការ",
+ "weekday": "ថ្ងៃធ្វើការ",
+ "months": "ខែ",
+ "month": "ខែ",
+ "years": "ឆ្នាំ",
+ "year": "ឆ្នាំ"
+ },
+ "dayNames": [
+ "ថ្ងៃអាទិត្យ",
+ "ថ្ងៃច័ន្ទ",
+ "ថ្ងៃអង្គារ",
+ "ថ្ងៃពុធ",
+ "ថ្ងៃព្រហស្បតិ៍",
+ "ថ្ងៃសុក្រ",
+ "ថ្ងៃសៅរ៍"
+ ],
+ "monthNames": [
+ "ខែមករា",
+ "ខែកុម្ភៈ",
+ "ខែមីនា",
+ "ខែមេសា",
+ "ខែឧសភា",
+ "ខែមិថុនា",
+ "ខែកក្កដា",
+ "ខែសីហា",
+ "ខែកញ្ញា",
+ "ខែតុលា",
+ "ខែវិច្ឆិកា",
+ "ខែធ្នូ"
+ ]
+}
diff --git a/packages/webapp/src/main.jsx b/packages/webapp/src/main.jsx
index c575486a3c..14b77a0715 100644
--- a/packages/webapp/src/main.jsx
+++ b/packages/webapp/src/main.jsx
@@ -70,7 +70,7 @@ if (import.meta.env.VITE_SENTRY_DSN) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [new Integrations.BrowserTracing()],
- release: '3.12.0',
+ release: '3.12.1',
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
diff --git a/packages/webapp/src/routes/SurveyRoutes.tsx b/packages/webapp/src/routes/SurveyRoutes.tsx
new file mode 100644
index 0000000000..c1bec1d65f
--- /dev/null
+++ b/packages/webapp/src/routes/SurveyRoutes.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2026 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 { Redirect, Switch, Route, useParams } from 'react-router-dom';
+import Survey from '../containers/Insights/Survey';
+import { getResultsComponent } from '../containers/Insights/Survey/surveyConfig';
+
+// Renders the results component the survey declares (or the generic thank-you page).
+const SurveyResults = () => {
+ const { surveyId } = useParams<{ surveyId: string }>();
+ const ResultsComponent = getResultsComponent(surveyId);
+ return ;
+};
+
+const SurveyRoutes = ({ isCompactSideMenu }: { isCompactSideMenu: boolean }) => {
+ return (
+
+
+
+
+
+
+
+ } />
+
+ );
+};
+
+export default SurveyRoutes;
diff --git a/packages/webapp/src/routes/TapeRoutes.tsx b/packages/webapp/src/routes/TapeRoutes.tsx
deleted file mode 100644
index 6f84628d32..0000000000
--- a/packages/webapp/src/routes/TapeRoutes.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2026 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 React from 'react';
-import { Redirect, Switch, Route } from 'react-router-dom';
-import { useSelector } from 'react-redux';
-import { getSurveyVersion } from '../containers/Insights/TapeSurvey/getSurveyVersion';
-import { userFarmSelector } from '../containers/userFarmSlice';
-
-const TapeSurvey = React.lazy(() => import('../containers/Insights/TapeSurvey'));
-const TapeResults = React.lazy(() => import('../containers/Insights/TapeSurvey/TapeResults'));
-
-const TapeRoutes = ({ isCompactSideMenu }: { isCompactSideMenu: boolean }) => {
- // @ts-expect-error -- userFarmSelector issue
- const { country_code } = useSelector(userFarmSelector);
- const surveyVersion = getSurveyVersion(country_code);
-
- if (!surveyVersion) {
- return ;
- }
-
- return (
-
-
-
-
-
-
-
- } />
-
- );
-};
-
-export default TapeRoutes;
diff --git a/packages/webapp/src/routes/index.jsx b/packages/webapp/src/routes/index.jsx
index 43aeafe67d..37ed5405b5 100644
--- a/packages/webapp/src/routes/index.jsx
+++ b/packages/webapp/src/routes/index.jsx
@@ -60,7 +60,7 @@ const SoilOM = React.lazy(() => import('../containers/Insights/SoilOM'));
const LabourHappiness = React.lazy(() => import('../containers/Insights/LabourHappiness'));
const Biodiversity = React.lazy(() => import('../containers/Insights/Biodiversity'));
const Prices = React.lazy(() => import('../containers/Insights/Prices'));
-const TapeRoutes = React.lazy(() => import('./TapeRoutes'));
+const SurveyRoutes = React.lazy(() => import('./SurveyRoutes'));
const ExpiredTokenScreen = React.lazy(() => import('../containers/ExpiredTokenScreen'));
const Map = React.lazy(() => import('../containers/Map'));
@@ -69,8 +69,8 @@ const LocationDetailsRoutes = React.lazy(() => import('./LocationDetailsRoutes')
// Import config separately to access allLocationTypes at render time
import { allLocationTypes } from './locationRouteConfig';
-const PostLocationDetailForm = React.lazy(() =>
- import('../containers/LocationDetails/PostLocationDetailForm'),
+const PostLocationDetailForm = React.lazy(
+ () => import('../containers/LocationDetails/PostLocationDetailForm'),
);
const CropManagement = React.lazy(() => import('../containers/Crop/CropManagement'));
@@ -84,34 +84,34 @@ const AddCropVariety = React.lazy(() => import('../containers/AddCropVariety/Add
const EditCropVariety = 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'));
@@ -120,60 +120,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'));
@@ -187,44 +190,44 @@ 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 SensorList = React.lazy(() => import('../containers/SensorList'));
const SensorReadings = React.lazy(() => import('../containers/SensorReadings/v2'));
const IrrigationPrescription = React.lazy(() => import('../containers/IrrigationPrescription'));
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 }) => {
@@ -511,7 +514,7 @@ const Routes = ({ isCompactSideMenu }) => {
} />
} />
-
+
} />
} />
@@ -842,7 +845,7 @@ const Routes = ({ isCompactSideMenu }) => {
} />
} />
-
+
} />
} />
diff --git a/packages/webapp/src/store/actionTypes.js b/packages/webapp/src/store/actionTypes.js
index 85d64b64f8..dc7f744fdd 100644
--- a/packages/webapp/src/store/actionTypes.js
+++ b/packages/webapp/src/store/actionTypes.js
@@ -13,6 +13,11 @@
* GNU General Public License for more details, see .
*/
+import { createAction } from '@reduxjs/toolkit';
+
export const ActionTypes = {
- SWITCH_FARMS: 'chooseFarmFlowReducer/startSwitchFarmModal',
+ RESET_FARM_STATE: 'farmStateReducer/reset',
};
+
+// Dispatched by clearOldFarmStateSaga (switch, add, and invite)
+export const resetFarmStateReducer = createAction(ActionTypes.RESET_FARM_STATE);
diff --git a/packages/webapp/src/store/api/apiTags.ts b/packages/webapp/src/store/api/apiTags.ts
index dd0f96c5b0..d80d7b745c 100644
--- a/packages/webapp/src/store/api/apiTags.ts
+++ b/packages/webapp/src/store/api/apiTags.ts
@@ -55,7 +55,7 @@ export const FarmTags = [
'SensorReadings',
'Weather',
'MarketDirectoryInfo',
- 'TapeSurvey',
+ 'SurveyResponse',
'FarmNote',
'FarmNotesRead',
] as const;
diff --git a/packages/webapp/src/store/api/tapeSurveyApi.ts b/packages/webapp/src/store/api/surveyApi.ts
similarity index 58%
rename from packages/webapp/src/store/api/tapeSurveyApi.ts
rename to packages/webapp/src/store/api/surveyApi.ts
index f8d04d8dae..d92a66d4c6 100644
--- a/packages/webapp/src/store/api/tapeSurveyApi.ts
+++ b/packages/webapp/src/store/api/surveyApi.ts
@@ -14,31 +14,33 @@
*/
import { api } from './apiSlice';
-import { tapeSurveyUrl } from '../../apiConfig';
+import { surveyResponseUrl } from '../../apiConfig';
import { DO_CDN_URL } from '../../util/constants';
-export interface TapeSurveyRecord {
+export interface SurveyResponseRecord {
id: string;
farm_id: string;
+ survey_key: string;
survey_response: Record;
survey_version: string;
project_id: string;
survey_step: string;
}
-export interface AddTapeSurveyReqBody {
+export interface AddSurveyResponseReqBody {
farm_id: string;
+ survey_key: string;
survey_response: Record;
}
-export const tapeSurveyApi = api.injectEndpoints({
+export const surveyApi = api.injectEndpoints({
endpoints: (build) => ({
// Fetches the SurveyJS JSON definition from DO CDN.
// Uses queryFn (not query) because this bypasses the LiteFarm API base URL and auth headers.
- getTapeSurveyJson: build.query, string>({
- queryFn: async (versionKey) => {
+ getSurveyJson: build.query, { cdnDirectory: string; version: string }>({
+ queryFn: async ({ cdnDirectory, version }) => {
try {
- const response = await fetch(`${DO_CDN_URL}/tape_surveys/${versionKey}.json`);
+ const response = await fetch(`${DO_CDN_URL}/${cdnDirectory}/${version}.json`);
if (!response.ok) {
return {
error: { status: response.status, data: `Failed to fetch survey JSON` },
@@ -51,24 +53,29 @@ export const tapeSurveyApi = api.injectEndpoints({
}
},
}),
- getTapeSurvey: build.query({
- query: () => `${tapeSurveyUrl}`,
- providesTags: ['TapeSurvey'],
+ getLatestSurveyResponse: build.query({
+ query: ({ surveyKey }) => ({
+ url: surveyResponseUrl,
+ params: { survey_key: surveyKey },
+ }),
+ providesTags: (_result, _error, { surveyKey }) => [{ type: 'SurveyResponse', id: surveyKey }],
}),
- addTapeSurvey: build.mutation({
+ addSurveyResponse: build.mutation({
query: (body) => ({
- url: tapeSurveyUrl,
+ url: surveyResponseUrl,
method: 'POST',
body,
}),
- invalidatesTags: ['TapeSurvey'],
+ invalidatesTags: (_result, _error, { survey_key }) => [
+ { type: 'SurveyResponse', id: survey_key },
+ ],
}),
}),
});
export const {
- useGetTapeSurveyJsonQuery,
- useGetTapeSurveyQuery,
- useAddTapeSurveyMutation,
+ useGetSurveyJsonQuery,
+ useGetLatestSurveyResponseQuery,
+ useAddSurveyResponseMutation,
usePrefetch,
-} = tapeSurveyApi;
+} = surveyApi;
diff --git a/packages/webapp/src/store/reducer.js b/packages/webapp/src/store/reducer.js
index 5987ca8a97..97a4543d68 100644
--- a/packages/webapp/src/store/reducer.js
+++ b/packages/webapp/src/store/reducer.js
@@ -71,7 +71,7 @@ import fieldWorkTypeReducer from '../containers/fieldWorkTypeSlice';
import irrigationTaskReducer from '../containers/slice/taskSlice/irrigationTaskSlice';
import irrigationTaskTypesReducer from '../containers/irrigationTaskTypesSlice';
import revenueTypeReducer from '../containers/revenueTypeSlice';
-import tapeSurveyReducer from '../containers/Insights/TapeSurvey/tapeSurveySlice';
+import surveyDraftReducer from '../containers/Insights/Survey/surveyDraftSlice';
import { ActionTypes } from './actionTypes';
// all the initial state for the forms
@@ -183,7 +183,7 @@ const entitiesReducer = combineReducers({
const farmStateReducer = combineReducers({
notificationReducer,
- tapeSurveyReducer,
+ surveyDraftReducer,
});
const persistedStateReducer = combineReducers({
@@ -231,8 +231,8 @@ const appReducer = combineReducers({
});
const rootReducer = (state, action) => {
- if (state && action.type === ActionTypes.SWITCH_FARMS) {
- // selectively only reset farmStateReducer state when switching farms
+ if (state && action.type === ActionTypes.RESET_FARM_STATE) {
+ // Drop the farmStateReducer branch so combineReducers re-initialises it
const { farmStateReducer, ...otherReducers } = state;
state = otherReducers;
}
diff --git a/packages/webapp/src/stories/Finances/RevenueForm.stories.jsx b/packages/webapp/src/stories/Finances/RevenueForm.stories.jsx
index 2f7302c78d..b83fad9c36 100644
--- a/packages/webapp/src/stories/Finances/RevenueForm.stories.jsx
+++ b/packages/webapp/src/stories/Finances/RevenueForm.stories.jsx
@@ -17,6 +17,8 @@ import RevenueForm from '../../components/Forms/RevenueForm';
import { getEntityTypeDefaultValues } from '../../containers/Finances/EntitySaleInputs';
import { componentDecorators } from '../Pages/config/Decorators';
+const system = 'metric';
+
const cropSale = {
sale_id: 17,
customer_name: 'Name',
@@ -207,7 +209,7 @@ CropSaleDetail.args = {
revenueTypeOptions,
onRetire: () => {},
revenueTypes,
- entitySaleDefaultValues: getEntityTypeDefaultValues(cropSale, 'crop'),
+ entitySaleDefaultValues: getEntityTypeDefaultValues(cropSale, 'crop', system),
};
export const AnimalSaleDetail = Template.bind({});
@@ -218,5 +220,5 @@ AnimalSaleDetail.args = {
revenueTypeOptions,
onRetire: () => {},
revenueTypes,
- entitySaleDefaultValues: getEntityTypeDefaultValues(animalSale, 'animal'),
+ entitySaleDefaultValues: getEntityTypeDefaultValues(animalSale, 'animal', system),
};
diff --git a/packages/webapp/src/util/smartIrrigation.ts b/packages/webapp/src/util/smartIrrigation.ts
index 3a1b0ff9ff..14d98d107a 100644
--- a/packages/webapp/src/util/smartIrrigation.ts
+++ b/packages/webapp/src/util/smartIrrigation.ts
@@ -37,5 +37,5 @@ export const createESciReportLink = ({
return;
}
- return `https://app.esci.io/${organisation_url_name}/${system_url_name}/irrigation-report`;
+ return `https://app.esci.io/${organisation_url_name}/reports/${system_url_name}`;
};