bulk plant status update

pull/1647/head
gabrielburnworth 2019-12-30 12:13:23 -08:00
parent d854cb9b22
commit 53b09e70a4
5 changed files with 164 additions and 53 deletions

View File

@ -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;

View File

@ -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",

View File

@ -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>;

View File

@ -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;

View File

@ -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>;