diff --git a/package-lock.json b/package-lock.json
index 6518d2b..6292c47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10911,7 +10911,7 @@
},
"node_modules/protobuf-ts": {
"version": "1.0.0",
- "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#1b6076608722fff5290457b077604bb0b38d5499",
+ "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#70351a87aa4a83bd6274adf8e3b7937090ab4cc2",
"dependencies": {
"protobufjs": "^6.8.8"
}
diff --git a/src/component/ComponentCard.tsx b/src/component/ComponentCard.tsx
index bf83696..6d430d5 100644
--- a/src/component/ComponentCard.tsx
+++ b/src/component/ComponentCard.tsx
@@ -285,7 +285,6 @@ export default function ComponentCard(props: Props) {
// ) : (
diff --git a/src/field/FieldList.tsx b/src/field/FieldList.tsx
index f30d6dd..7362fd8 100644
--- a/src/field/FieldList.tsx
+++ b/src/field/FieldList.tsx
@@ -1,7 +1,13 @@
import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
Box,
Button,
+ Grid2,
+ IconButton,
Paper,
+ Stack,
Tab,
Table,
TableBody,
@@ -13,15 +19,20 @@ import {
Typography
} from "@mui/material";
import React, { useCallback, useEffect, useState } from "react";
-import { useMobile, useSnackbar } from "hooks";
-import { useGlobalState, useFieldAPI } from "providers";
+import { useMobile, useSnackbar, useUserAPI } from "hooks";
+import { useGlobalState, useFieldAPI, useJohnDeereProxyAPI, useCNHiProxyAPI } from "providers";
//import HarvestTable from "harvestPlan/HarvestTable";
-import { Field } from "models";
+import { Field, fieldScope, teamScope } from "models";
import GrainDescriber from "grain/GrainDescriber";
import { pond } from "protobuf-ts/pond";
import HarvestSettings from "harvestPlan/HarvestSettings";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import FieldMinimap from "./Fieldminimap";
+import FieldActions from "./FieldActions";
+import FieldSettings from "./FieldSettings";
+import { ExpandMore, Settings } from "@mui/icons-material";
+import { cloneDeep } from "lodash";
+import ShareAllFields from "./ShareAllFields";
interface TabPanelProps {
children?: React.ReactNode;
@@ -29,61 +40,73 @@ interface TabPanelProps {
value: any;
}
-function TabPanelMine(props: TabPanelProps) {
- const { children, value, index, ...other } = props;
-
- return (
-
- {value === index && {children}}
-
- );
-}
-
export default function FieldList() {
const [{ as }] = useGlobalState();
const isMobile = useMobile();
const fieldAPI = useFieldAPI();
+ const jdAPI = useJohnDeereProxyAPI();
+ const cnhAPI = useCNHiProxyAPI();
const { openSnack } = useSnackbar();
- const [value, setValue] = React.useState(0);
+ const [tabValue, setTabValue] = React.useState(0);
const [openHarvestSettings, setOpenHarvestSettings] = useState(false);
- const [fieldForPlan, setFieldForPlan] = useState(Field.create());
- const [fields, setFields] = useState([]); //all fields
- const [adaptiveFields, setAdaptiveFields] = useState([])
- const [jdFields, setJDFields] = useState([])
- const [cnhFields, setCNHFields] = useState([])
+ // const [fieldForPlan, setFieldForPlan] = useState(Field.create());
+ const [fields, setFields] = useState([]);
+ const [total, setTotal] = useState(0)
+ const [rowsPerPage, setRowsPerPage] = useState(5)
+ const [page, setPage] = useState(0)
+ const [selectedField, setSelectedField] = useState()
+ const [openFieldSettings, setOpenFieldSettings] = useState(false)
+ const [shareOpen, setShareOpen] = useState(false)
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
- setValue(newValue);
+ setTabValue(newValue);
+ setPage(0)
};
- const loadFields = useCallback(() => {
+ const loadAdaptiveFields = useCallback(() => {
fieldAPI
- .listFields(500, 0, "asc", "fieldName", undefined, as)
+ .listFields(rowsPerPage, page*rowsPerPage, "asc", "fieldName", undefined, as)
.then(resp => {
- setAdaptiveFields(resp.data.fields.map(f => Field.any(f)));
- // resp.data.fields.forEach(f => {
- // if (f.settings) {
- // fieldAPI.updateField(f.settings.key, f.settings);
- // }
- // });
+ setFields(resp.data.fields.map(f => Field.create(f)));
+ setTotal(resp.data.total)
})
.catch(err => {
- openSnack("Failed to load Field Mapping");
+ openSnack("Failed to load Fields");
});
- }, [fieldAPI, as, openSnack]);
+ }, [fieldAPI, as, openSnack, page, rowsPerPage]);
+
+ const loadJDFields = useCallback(() => {
+ jdAPI.listFields(rowsPerPage, page*rowsPerPage, as)
+ .then(resp => {
+ resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
+ }).catch(err => {
+ openSnack("Failed to load John Deere Fields");
+ })
+ },[jdAPI, as, openSnack, page, rowsPerPage])
+
+ const loadCNHFields = useCallback(() => {
+ cnhAPI.listFields(rowsPerPage, page*rowsPerPage, as)
+ .then(resp => {
+ resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
+ }).catch(err => {
+ openSnack("Failed to load John Deere Fields");
+ })
+ },[cnhAPI, as, openSnack, page, rowsPerPage])
useEffect(() => {
- loadFields();
- }, [loadFields]);
-
- useEffect(()=>{
- setFields(adaptiveFields.concat(jdFields, cnhFields))
- },[adaptiveFields,jdFields,cnhFields])
+ //based on the current tab load the correct fields
+ switch(tabValue){
+ case 0:
+ loadAdaptiveFields();
+ break;
+ case 1:
+ loadJDFields();
+ break;
+ case 2:
+ loadCNHFields();
+ break;
+ }
+ }, [loadAdaptiveFields, loadJDFields, loadCNHFields, tabValue]);
const calcTotalAcres = (fields: Field[]) => {
let totalAcres = 0;
@@ -94,6 +117,19 @@ export default function FieldList() {
return totalAcres;
};
+ const fieldActions = (field: Field) => {
+ return (
+
+ {
+ setSelectedField(field)
+ setOpenFieldSettings(true)
+ }}>
+ {}}/>
+
+ )
+ }
+
const columns: Column[] = [
{
title: "View",
@@ -108,26 +144,25 @@ const columns: Column[] = [
{
title: "Field Name",
render: row => {
- return {row.name()}
+ return {row.name()}
}
},
{
title: "Current Crop",
render: row => {
- return {row.crop()}
+ return {GrainDescriber(row.crop()).name}
}
},
{
title: "Acres",
render: row => {
- return {row.acres()}
+ return {row.acres()}
}
},
{
title: "Actions",
- render: row => {
- return Field Actions Here
- }
+ hidden: tabValue > 0,
+ render: row => fieldActions(row)
}
]
@@ -136,43 +171,126 @@ const columns: Column[] = [
columns={columns}
rows={fields}
- setPage={()=>{}}
- handleRowsPerPageChange={()=>{}}
- page={0}
- pageSize={1}
- total={fields.length}
+ setPage={(newPage)=>{
+ setPage(newPage)
+ }}
+ handleRowsPerPageChange={(e)=>{setRowsPerPage(e.target.value)}}
+ page={page}
+ pageSize={rowsPerPage}
+ total={total}
+ resizeable
+ loadMore={() => {
+ let currentFields = cloneDeep(fields)
+ switch(tabValue){
+ case 0:
+ fieldAPI.listFields(rowsPerPage, currentFields.length, "asc", "fieldName", undefined, as).then(resp => {
+ setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f))))
+ }).catch(err => {
+ openSnack("Failed to load more fields")
+ })
+ break;
+ case 1:
+ jdAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
+ resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
+ }).catch(err => {
+ openSnack("Failed to load more fields")
+ })
+ break;
+ case 2:
+ cnhAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
+ resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
+ }).catch(err => {
+ openSnack("Failed to load more fields")
+ })
+ break;
+ }
+ }}
+ renderMobile={(row, index) => {
+ return (
+
+ }>
+
+
+
+ {row.name()}
+
+
+
+ {fieldActions(row)}
+
+
+
+
+
+
+
+
+
+
+
+
+ {row.grainName()}
+ {row.acres()} Acres
+
+
+
+
+
+ )
+ }}
/>
)
}
return (
+ {isMobile &&
+
+ }
-
+ {!isMobile &&
+
+ }
-
- {fieldTable(fields)}
-
-
- {fieldTable(adaptiveFields)}
-
-
- {fieldTable(jdFields)}
-
-
- {fieldTable(cnhFields)}
-
- setOpenHarvestSettings(false)}
field={fieldForPlan}
+ /> */}
+ {
+ //will need to re-load the fields after one is deleted
+ loadAdaptiveFields()
+ }}
+ open={openFieldSettings}
+ onClose={() => {
+ setOpenFieldSettings(false);
+ }}
/>
+ {setShareOpen(false)}}/>
);
}
\ No newline at end of file
diff --git a/src/field/Fieldminimap.tsx b/src/field/Fieldminimap.tsx
index 762a622..0752610 100644
--- a/src/field/Fieldminimap.tsx
+++ b/src/field/Fieldminimap.tsx
@@ -40,16 +40,17 @@ export default function FieldMinimap(props: Props) {
return geoCollection ?
(
)
diff --git a/src/field/ShareAllFields.tsx b/src/field/ShareAllFields.tsx
new file mode 100644
index 0000000..366ee16
--- /dev/null
+++ b/src/field/ShareAllFields.tsx
@@ -0,0 +1,43 @@
+import { Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
+import ResponsiveDialog from "common/ResponsiveDialog";
+
+interface Props {
+ open: boolean
+ close: () => void
+}
+
+export default function ShareAllFields(props: Props) {
+ const {open, close} = props
+
+ const share = () => {
+ //if sharing to team use shareAllByKey
+
+ //if sharing to user use shareAll
+ }
+
+ const target = () => {
+ // toggle for whether sharing to user or team
+ // if sharing to user have a text field
+ // if sharing to team have the team selector
+ }
+
+ const permissions = () => {
+ // the checkboxes of permissions
+ }
+
+ const actions = () => {
+ //the buttons to shaare or cancel
+ }
+
+ return (
+
+ Share All Fields
+
+ Content for selecting target and permissions
+
+
+ Buttons for cancel and confirm
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/maps/mapLayers/geoMapLayer.tsx b/src/maps/mapLayers/geoMapLayer.tsx
index 5b90854..1923b64 100644
--- a/src/maps/mapLayers/geoMapLayer.tsx
+++ b/src/maps/mapLayers/geoMapLayer.tsx
@@ -6,7 +6,7 @@ interface Props {
objectCollection: FeatureCollection;
measurementCollection?: FeatureCollection;
showTitle?: boolean;
- setInteractiveLayers: (layers: string[]) => void;
+ setInteractiveLayers?: (layers: string[]) => void;
}
export default function GeoMapLayer(props: Props) {
@@ -18,7 +18,9 @@ export default function GeoMapLayer(props: Props) {
// }, [measurementCollection, objectCollection]);
useEffect(() => {
- setInteractiveLayers(["shapefill", "shapeborder", "measurementBorder"]);
+ if(setInteractiveLayers){
+ setInteractiveLayers(["shapefill", "shapeborder", "measurementBorder"]);
+ }
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
@@ -60,6 +62,7 @@ export default function GeoMapLayer(props: Props) {
/>
)}
+ {measurementCollection &&
+ }
);
}
diff --git a/src/models/Field.ts b/src/models/Field.ts
index 3816fce..d2061d1 100644
--- a/src/models/Field.ts
+++ b/src/models/Field.ts
@@ -10,14 +10,16 @@ import { LngLat, LngLatBounds } from "mapbox-gl";
export class Field {
public settings: pond.FieldSettings = pond.FieldSettings.create();
public status: pond.FieldStatus = pond.FieldStatus.create();
- public permissions: pond.Permission[] = [];
+ public permissions: pond.Permission[] = [];
public static create(pf?: pond.Field): Field {
let my = new Field();
if (pf) {
- my.settings = pond.FieldSettings.fromObject(cloneDeep(or(pf.settings, {})));
- my.status = pond.FieldStatus.fromObject(cloneDeep(or(pf.status, {})));
- my.permissions = pf.fieldPermissions;
+ //the from object is required here to deal with a protobuf quirk where enums would be seen as strings at runtime, but the code sees them as the actual value
+ let field = pond.Field.fromObject(cloneDeep(or(pf, pond.Field.create())))
+ my.settings = field.settings ? field.settings : pond.FieldSettings.create()
+ my.status = field.status ? field.status : pond.FieldStatus.create()
+ my.permissions = field.fieldPermissions
}
return my;
}
diff --git a/src/providers/pond/fieldAPI.tsx b/src/providers/pond/fieldAPI.tsx
index 17d188e..93309df 100644
--- a/src/providers/pond/fieldAPI.tsx
+++ b/src/providers/pond/fieldAPI.tsx
@@ -5,6 +5,7 @@ import { useGlobalState } from "providers";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { or } from "utils";
import { pondURL } from "./pond";
+import { permissionToString } from "pbHelpers/Permission";
export interface IFieldAPIContext {
addField: (field: pond.FieldSettings, otherTeam?: string) => Promise;
@@ -18,14 +19,6 @@ export interface IFieldAPIContext {
otherTeam?: string,
asRoot?: boolean
) => Promise>;
- fieldsPageData: (
- limit: number,
- offset: number,
- order?: "asc" | "desc",
- orderBy?: string,
- search?: string,
- otherTeam?: string,
- ) => Promise>
removeField: (key: string, otherTeam?: string) => Promise>;
updateField: (
key: string,
@@ -33,6 +26,14 @@ export interface IFieldAPIContext {
asRoot?: true,
otherTeam?: string
) => Promise>;
+ shareAll: (
+ email: string,
+ permissions: pond.Permission[],
+ ) => Promise>
+ shareAllByKey: (
+ key: string,
+ permissions: pond.Permission[]
+ ) => Promise>
}
export const FieldAPIContext = createContext({} as IFieldAPIContext);
@@ -88,30 +89,6 @@ export default function FieldProvider(props: PropsWithChildren) {
);
};
- const fieldsPageData = (
- limit: number,
- offset: number,
- order?: "asc" | "desc",
- orderBy?: string,
- search?: string,
- otherTeam?: string,
- ) => {
-const view = otherTeam ? otherTeam : as
- return get(
- pondURL(
- "/fieldsPage" +
- "?limit=" +
- limit +
- "&offset=" +
- offset +
- ("&order=" + or(order, "asc")) +
- ("&by=" + or(orderBy, "key")) +
- (view ? "&as=" + view : "") +
- (search ? "&search=" + search : "")
- )
- );
- }
-
const updateField = (key: string, field: pond.FieldSettings, asRoot?: boolean, otherTeam?: string) => {
const view = otherTeam ? otherTeam : as
if (view)
@@ -125,6 +102,36 @@ const view = otherTeam ? otherTeam : as
);
};
+ const shareAll = (email: string, permissions: pond.Permission[]) => {
+ let url = pondURL("/fields/shareAll")
+ if (as) url = url + "?as=" + as
+ return new Promise((resolve, reject) => {
+ post(url, {
+ email: email,
+ permissions: permissions.map(permission => permissionToString(permission))
+ }).then(resp => {
+ return resolve(resp)
+ }).catch(err => {
+ return reject(err)
+ })
+ })
+ }
+
+ const shareAllByKey = (key: string, permissions: pond.Permission[]) => {
+ let url = pondURL("/fields/shareAllByKey")
+ if (as) url = url + "?as=" + as
+ return new Promise((resolve, reject) => {
+ post(url, {
+ key: key,
+ permissions: permissions.map(permission => permissionToString(permission))
+ }).then(resp => {
+ return resolve(resp)
+ }).catch(err => {
+ return reject(err)
+ })
+ })
+ }
+
return (
{children}
diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx
index 2311afd..91b7d6a 100644
--- a/src/providers/pond/permissionAPI.tsx
+++ b/src/providers/pond/permissionAPI.tsx
@@ -135,7 +135,6 @@ export default function PermissionProvider(props: PropsWithChildren) {
let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey")
if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`)
return new Promise((resolve, reject) => {
-
post(url, {
key: key,
permissions: permissions.map(permission => permissionToString(permission))