bulk plant status update
parent
d854cb9b22
commit
53b09e70a4
|
@ -303,12 +303,25 @@
|
|||
margin-bottom: 0px;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.buttonrow {
|
||||
float:left;
|
||||
.button-row {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
.plant-status-bulk-update {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
.filter-search {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 4.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-content {
|
||||
padding-top: 10rem;
|
||||
padding-top: 15rem;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
padding-bottom: 5rem;
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
PlantPanel, PlantPanelProps, EditPlantStatus, EditPlantStatusProps,
|
||||
PlantPanel, PlantPanelProps, EditPlantStatusProps,
|
||||
EditDatePlantedProps, EditDatePlanted, EditPlantLocationProps,
|
||||
EditPlantLocation
|
||||
EditPlantLocation,
|
||||
} from "../plant_panel";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { FormattedPlantInfo } from "../map_state_to_props";
|
||||
|
@ -13,6 +18,11 @@ import { clickButton } from "../../../__test_support__/helpers";
|
|||
import { history } from "../../../history";
|
||||
import moment from "moment";
|
||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { edit } from "../../../api/crud";
|
||||
import {
|
||||
EditPlantStatus, PlantStatusBulkUpdateProps, PlantStatusBulkUpdate
|
||||
} from "../edit_plant_status";
|
||||
|
||||
describe("<PlantPanel/>", () => {
|
||||
const info: FormattedPlantInfo = {
|
||||
|
@ -120,6 +130,42 @@ describe("<EditPlantStatus />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("<PlantStatusBulkUpdate />", () => {
|
||||
const fakeProps = (): PlantStatusBulkUpdateProps => ({
|
||||
plants: [],
|
||||
selected: [],
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("doesn't update plant statuses", () => {
|
||||
const p = fakeProps();
|
||||
const plant1 = fakePlant();
|
||||
const plant2 = fakePlant();
|
||||
p.plants = [plant1, plant2];
|
||||
p.selected = [plant1.uuid];
|
||||
const wrapper = shallow(<PlantStatusBulkUpdate {...p} />);
|
||||
window.confirm = jest.fn(() => false);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: "planted" });
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(edit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates plant statuses", () => {
|
||||
const p = fakeProps();
|
||||
const plant1 = fakePlant();
|
||||
const plant2 = fakePlant();
|
||||
const plant3 = fakePlant();
|
||||
p.plants = [plant1, plant2, plant3];
|
||||
p.selected = [plant1.uuid, plant2.uuid];
|
||||
const wrapper = shallow(<PlantStatusBulkUpdate {...p} />);
|
||||
window.confirm = jest.fn(() => true);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: "planted" });
|
||||
expect(window.confirm).toHaveBeenCalledWith(
|
||||
"Change the plant status to 'planted' for 2 plants?");
|
||||
expect(edit).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<EditDatePlanted />", () => {
|
||||
const fakeProps = (): EditDatePlantedProps => ({
|
||||
uuid: "Plant.0.0",
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import * as React from "react";
|
||||
import { FBSelect, DropDownItem } from "../../ui";
|
||||
import { PlantOptions } from "../interfaces";
|
||||
import { PlantStage } from "farmbot";
|
||||
import moment from "moment";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TaggedPlant } from "../map/interfaces";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
import { edit, save } from "../../api/crud";
|
||||
import { EditPlantStatusProps } from "./plant_panel";
|
||||
|
||||
const PLANT_STAGES: DropDownItem[] = [
|
||||
{ value: "planned", label: t("Planned") },
|
||||
{ value: "planted", label: t("Planted") },
|
||||
{ value: "sprouted", label: t("Sprouted") },
|
||||
{ value: "harvested", label: t("Harvested") },
|
||||
];
|
||||
|
||||
const PLANT_STAGES_DDI = {
|
||||
[PLANT_STAGES[0].value]: {
|
||||
label: PLANT_STAGES[0].label,
|
||||
value: PLANT_STAGES[0].value
|
||||
},
|
||||
[PLANT_STAGES[1].value]: {
|
||||
label: PLANT_STAGES[1].label,
|
||||
value: PLANT_STAGES[1].value
|
||||
},
|
||||
[PLANT_STAGES[2].value]: {
|
||||
label: PLANT_STAGES[2].label,
|
||||
value: PLANT_STAGES[2].value
|
||||
},
|
||||
[PLANT_STAGES[3].value]: {
|
||||
label: PLANT_STAGES[3].label,
|
||||
value: PLANT_STAGES[3].value
|
||||
},
|
||||
};
|
||||
|
||||
/** Change `planted_at` value based on `plant_stage` update. */
|
||||
const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => {
|
||||
const update: PlantOptions = { plant_stage };
|
||||
switch (plant_stage) {
|
||||
case "planned":
|
||||
update.planted_at = undefined;
|
||||
break;
|
||||
case "planted":
|
||||
update.planted_at = moment().toISOString();
|
||||
}
|
||||
return update;
|
||||
};
|
||||
|
||||
/** Select a `plant_stage` for a plant. */
|
||||
export function EditPlantStatus(props: EditPlantStatusProps) {
|
||||
const { plantStatus, updatePlant, uuid } = props;
|
||||
return <FBSelect
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={PLANT_STAGES_DDI[plantStatus]}
|
||||
onChange={ddi =>
|
||||
updatePlant(uuid, getUpdateByPlantStage(ddi.value as PlantStage))} />;
|
||||
}
|
||||
|
||||
export interface PlantStatusBulkUpdateProps {
|
||||
plants: TaggedPlant[];
|
||||
selected: UUID[];
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
/** Update `plant_stage` for multiple plants at once. */
|
||||
export const PlantStatusBulkUpdate = (props: PlantStatusBulkUpdateProps) =>
|
||||
<div className="plant-status-bulk-update">
|
||||
<p>{t("update plant status to")}</p>
|
||||
<FBSelect
|
||||
key={JSON.stringify(props.selected)}
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={undefined}
|
||||
customNullLabel={t("Select a status")}
|
||||
onChange={ddi => {
|
||||
const plant_stage = ddi.value as PlantStage;
|
||||
const update = getUpdateByPlantStage(plant_stage);
|
||||
const plants = props.plants.filter(plant =>
|
||||
props.selected.includes(plant.uuid)
|
||||
&& plant.kind === "Point"
|
||||
&& plant.body.plant_stage != plant_stage);
|
||||
plants.length > 0 && confirm(
|
||||
t("Change the plant status to '{{ status }}' for {{ num }} plants?",
|
||||
{ status: plant_stage, num: plants.length }))
|
||||
&& plants.map(plant => {
|
||||
props.dispatch(edit(plant, update));
|
||||
props.dispatch(save(plant.uuid));
|
||||
});
|
||||
}} />
|
||||
</div>;
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
import { FormattedPlantInfo } from "./map_state_to_props";
|
||||
import { round } from "../map/util";
|
||||
import { history } from "../../history";
|
||||
import { FBSelect, DropDownItem, BlurableInput, Row, Col } from "../../ui";
|
||||
import { BlurableInput, Row, Col } from "../../ui";
|
||||
import { PlantOptions } from "../interfaces";
|
||||
import { PlantStage } from "farmbot";
|
||||
import { Moment } from "moment";
|
||||
|
@ -14,6 +14,7 @@ import { parseIntInput } from "../../util";
|
|||
import { startCase } from "lodash";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TimeSettings } from "../../interfaces";
|
||||
import { EditPlantStatus } from "./edit_plant_status";
|
||||
|
||||
export interface PlantPanelProps {
|
||||
info: FormattedPlantInfo;
|
||||
|
@ -24,32 +25,6 @@ export interface PlantPanelProps {
|
|||
timeSettings?: TimeSettings;
|
||||
}
|
||||
|
||||
export const PLANT_STAGES: DropDownItem[] = [
|
||||
{ value: "planned", label: t("Planned") },
|
||||
{ value: "planted", label: t("Planted") },
|
||||
{ value: "sprouted", label: t("Sprouted") },
|
||||
{ value: "harvested", label: t("Harvested") },
|
||||
];
|
||||
|
||||
export const PLANT_STAGES_DDI = {
|
||||
[PLANT_STAGES[0].value]: {
|
||||
label: PLANT_STAGES[0].label,
|
||||
value: PLANT_STAGES[0].value
|
||||
},
|
||||
[PLANT_STAGES[1].value]: {
|
||||
label: PLANT_STAGES[1].label,
|
||||
value: PLANT_STAGES[1].value
|
||||
},
|
||||
[PLANT_STAGES[2].value]: {
|
||||
label: PLANT_STAGES[2].label,
|
||||
value: PLANT_STAGES[2].value
|
||||
},
|
||||
[PLANT_STAGES[3].value]: {
|
||||
label: PLANT_STAGES[3].label,
|
||||
value: PLANT_STAGES[3].value
|
||||
},
|
||||
};
|
||||
|
||||
interface EditPlantProperty {
|
||||
uuid: string;
|
||||
updatePlant(uuid: string, update: PlantOptions): void;
|
||||
|
@ -59,25 +34,6 @@ export interface EditPlantStatusProps extends EditPlantProperty {
|
|||
plantStatus: PlantStage;
|
||||
}
|
||||
|
||||
export function EditPlantStatus(props: EditPlantStatusProps) {
|
||||
const { plantStatus, updatePlant, uuid } = props;
|
||||
return <FBSelect
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={PLANT_STAGES_DDI[plantStatus]}
|
||||
onChange={e => {
|
||||
const plant_stage = e.value as PlantStage;
|
||||
const update: PlantOptions = { plant_stage };
|
||||
switch (plant_stage) {
|
||||
case "planned":
|
||||
update.planted_at = undefined;
|
||||
break;
|
||||
case "planted":
|
||||
update.planted_at = moment().toISOString();
|
||||
}
|
||||
updatePlant(uuid, update);
|
||||
}} />;
|
||||
}
|
||||
|
||||
export interface EditDatePlantedProps extends EditPlantProperty {
|
||||
datePlanted: Moment;
|
||||
timeSettings: TimeSettings;
|
||||
|
|
|
@ -15,6 +15,7 @@ import { t } from "../../i18next_wrapper";
|
|||
import { createGroup } from "../point_groups/actions";
|
||||
import { PanelColor } from "../panel_header";
|
||||
import { error } from "../../toast/toast";
|
||||
import { PlantStatusBulkUpdate } from "./edit_plant_status";
|
||||
|
||||
export const mapStateToProps = (props: Everything): SelectPlantsProps => ({
|
||||
selected: props.resources.consumers.farm_designer.selectedPlants,
|
||||
|
@ -57,7 +58,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
|
||||
ActionButtons = () =>
|
||||
<div className="panel-action-buttons">
|
||||
<div className="buttonrow">
|
||||
<div className="button-row">
|
||||
<button className="fb-button gray"
|
||||
onClick={() => this.props.dispatch(selectPlant(undefined))}>
|
||||
{t("Select none")}
|
||||
|
@ -69,7 +70,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
</button>
|
||||
</div>
|
||||
<label>{t("SELECTION ACTIONS")}</label>
|
||||
<div className="buttonrow">
|
||||
<div className="button-row">
|
||||
<button className="fb-button red"
|
||||
onClick={() => this.destroySelected(this.props.selected)}>
|
||||
{t("Delete")}
|
||||
|
@ -80,6 +81,10 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
: error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}>
|
||||
{t("Create group")}
|
||||
</button>
|
||||
<PlantStatusBulkUpdate
|
||||
plants={this.props.plants}
|
||||
selected={this.selected}
|
||||
dispatch={this.props.dispatch} />
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue