no executables farm event form
parent
271884f2d0
commit
a10267507b
|
@ -820,11 +820,9 @@ export namespace Content {
|
||||||
Doing so will limit the functionality of your FarmBot and
|
Doing so will limit the functionality of your FarmBot and
|
||||||
may cause unexpected behavior.`);
|
may cause unexpected behavior.`);
|
||||||
|
|
||||||
export const SET_TIMEZONE_HEADER =
|
export const MISSING_EXECUTABLE =
|
||||||
trim(`You must set a timezone before using the event feature.`);
|
trim(`You haven't made any sequences or regimens yet. To add an event,
|
||||||
|
first create a sequence or regimen.`);
|
||||||
export const SET_TIMEZONE_BODY =
|
|
||||||
trim(`Set device timezone here.`);
|
|
||||||
|
|
||||||
// Farmware
|
// Farmware
|
||||||
export const NO_IMAGES_YET =
|
export const NO_IMAGES_YET =
|
||||||
|
|
|
@ -431,12 +431,32 @@
|
||||||
.location-form {
|
.location-form {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
.note {
|
||||||
|
margin-top: 4rem;
|
||||||
|
}
|
||||||
|
.bp3-popover-wrapper {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-event-form {
|
||||||
|
.farm-event-repeat-options {
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-top: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.farm-event-repeat-form {
|
||||||
|
.add-event-repeat-frequency {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-farm-event-panel button.red,
|
.add-farm-event-panel button.red,
|
||||||
.edit-farm-event-panel button.red,
|
.edit-farm-event-panel button.red {
|
||||||
.add-farm-event-panel button.magenta,
|
|
||||||
.edit-farm-event-panel button.magenta {
|
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,3 @@
|
||||||
background: $gray;
|
background: $gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-farm-event-panel {
|
|
||||||
.note {
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-event-repeat-frequency {
|
|
||||||
min-height: 34px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
||||||
|
|
||||||
|
jest.mock("../../../api/crud", () => ({
|
||||||
|
destroy: jest.fn(),
|
||||||
|
init: jest.fn(() => ({ payload: { uuid: "fakeUuid" } })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../resources/actions", () => ({ destroyOK: jest.fn() }));
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { RawAddFarmEvent as AddFarmEvent } from "../add_farm_event";
|
import { RawAddFarmEvent as AddFarmEvent } from "../add_farm_event";
|
||||||
import { AddEditFarmEventProps } from "../../interfaces";
|
import { AddEditFarmEventProps } from "../../interfaces";
|
||||||
import {
|
import {
|
||||||
|
@ -10,8 +17,13 @@ import {
|
||||||
import {
|
import {
|
||||||
buildResourceIndex
|
buildResourceIndex
|
||||||
} from "../../../__test_support__/resource_index_builder";
|
} from "../../../__test_support__/resource_index_builder";
|
||||||
import { Actions } from "../../../constants";
|
|
||||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
|
import { destroyOK } from "../../../resources/actions";
|
||||||
|
import { init, destroy } from "../../../api/crud";
|
||||||
|
import { DesignerPanelHeader } from "../../designer_panel";
|
||||||
|
import { Content } from "../../../constants";
|
||||||
|
import { error } from "../../../toast/toast";
|
||||||
|
import { FarmEventForm } from "../edit_fe_form";
|
||||||
|
|
||||||
describe("<AddFarmEvent />", () => {
|
describe("<AddFarmEvent />", () => {
|
||||||
function fakeProps(): AddEditFarmEventProps {
|
function fakeProps(): AddEditFarmEventProps {
|
||||||
|
@ -49,21 +61,24 @@ describe("<AddFarmEvent />", () => {
|
||||||
expect(deleteBtn.props().hidden).toBeTruthy();
|
expect(deleteBtn.props().hidden).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("redirects", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
p.findFarmEventByUuid = jest.fn();
|
|
||||||
const wrapper = mount(<AddFarmEvent {...p} />);
|
|
||||||
expect(wrapper.text()).toContain("Loading");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with no executables", () => {
|
it("renders with no executables", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.findFarmEventByUuid = jest.fn();
|
p.findFarmEventByUuid = jest.fn();
|
||||||
p.sequencesById = {};
|
p.sequencesById = {};
|
||||||
p.regimensById = {};
|
p.regimensById = {};
|
||||||
const wrapper = mount(<AddFarmEvent {...p} />);
|
const wrapper = mount(<AddFarmEvent {...p} />);
|
||||||
expect(wrapper.text())
|
expect(wrapper.html()).toContain("fa-exclamation-triangle");
|
||||||
.toContain("You haven't made any regimens or sequences yet.");
|
});
|
||||||
|
|
||||||
|
it("changes temporary values", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.findFarmEventByUuid = jest.fn();
|
||||||
|
p.sequencesById = {};
|
||||||
|
p.regimensById = {};
|
||||||
|
const wrapper = mount<AddFarmEvent>(<AddFarmEvent {...p} />);
|
||||||
|
expect(wrapper.instance().getField("repeat")).toEqual("1");
|
||||||
|
wrapper.instance().setField("repeat", "2");
|
||||||
|
expect(wrapper.state().temporaryValues.repeat).toEqual("2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with no sequences", () => {
|
it("renders with no sequences", () => {
|
||||||
|
@ -74,27 +89,57 @@ describe("<AddFarmEvent />", () => {
|
||||||
p.regimensById = { "1": regimen };
|
p.regimensById = { "1": regimen };
|
||||||
const wrapper = mount(<AddFarmEvent {...p} />);
|
const wrapper = mount(<AddFarmEvent {...p} />);
|
||||||
wrapper.mount();
|
wrapper.mount();
|
||||||
expect(p.dispatch).toHaveBeenCalledWith({
|
expect(init).toHaveBeenCalledWith("FarmEvent",
|
||||||
type: Actions.INIT_RESOURCE,
|
expect.objectContaining({ executable_type: "Regimen" }));
|
||||||
payload: expect.objectContaining({
|
|
||||||
kind: "FarmEvent",
|
|
||||||
body: expect.objectContaining({ executable_type: "Regimen" })
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("cleans up when unmounting", () => {
|
it("cleans up when unmounting", () => {
|
||||||
const props = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = mount(<AddFarmEvent {...props} />);
|
const farmEvent = fakeFarmEvent("Sequence", 1);
|
||||||
wrapper.update();
|
farmEvent.body.id = 0;
|
||||||
const uuid: string = wrapper.state("uuid");
|
p.findFarmEventByUuid = () => farmEvent;
|
||||||
props.farmEvents[0].uuid = uuid;
|
const wrapper = mount(<AddFarmEvent {...p} />);
|
||||||
props.farmEvents[0].body.id = undefined;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
wrapper.update();
|
|
||||||
jest.resetAllMocks();
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
expect(props.dispatch).toHaveBeenCalled();
|
expect(destroy).toHaveBeenCalledWith(farmEvent.uuid, true);
|
||||||
expect(props.dispatch).toHaveBeenCalledWith(expect.any(Function));
|
});
|
||||||
|
|
||||||
|
it("doesn't delete saved farm events when unmounting", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const farmEvent = fakeFarmEvent("Sequence", 1);
|
||||||
|
farmEvent.body.id = 1;
|
||||||
|
p.findFarmEventByUuid = () => farmEvent;
|
||||||
|
const wrapper = mount(<AddFarmEvent {...p} />);
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(destroy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cleans up on back", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const farmEvent = fakeFarmEvent("Sequence", 1);
|
||||||
|
farmEvent.body.id = 0;
|
||||||
|
p.findFarmEventByUuid = () => farmEvent;
|
||||||
|
const wrapper = shallow(<AddFarmEvent {...p} />);
|
||||||
|
wrapper.find(DesignerPanelHeader).simulate("back");
|
||||||
|
expect(destroyOK).toHaveBeenCalledWith(farmEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't delete saved farm events on back", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const farmEvent = fakeFarmEvent("Sequence", 1);
|
||||||
|
farmEvent.body.id = 1;
|
||||||
|
p.findFarmEventByUuid = () => farmEvent;
|
||||||
|
const wrapper = shallow(<AddFarmEvent {...p} />);
|
||||||
|
wrapper.find(DesignerPanelHeader).simulate("back");
|
||||||
|
expect(destroyOK).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows error on save", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.findFarmEventByUuid = jest.fn();
|
||||||
|
p.sequencesById = {};
|
||||||
|
p.regimensById = {};
|
||||||
|
const wrapper = shallow(<AddFarmEvent {...p} />);
|
||||||
|
wrapper.find(FarmEventForm).simulate("save");
|
||||||
|
expect(error).toHaveBeenCalledWith(Content.MISSING_EXECUTABLE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,7 +41,7 @@ describe("<EditFarmEvent />", () => {
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const wrapper = mount(<EditFarmEvent {...fakeProps()} />);
|
const wrapper = mount(<EditFarmEvent {...fakeProps()} />);
|
||||||
["Edit Event", "Sequence or Regimen", "fake", "Save"]
|
["Sequence or Regimen", "fake", "Save"]
|
||||||
.map(string => expect(wrapper.text()).toContain(string));
|
.map(string => expect(wrapper.text()).toContain(string));
|
||||||
const deleteBtn = wrapper.find("button").last();
|
const deleteBtn = wrapper.find("button").last();
|
||||||
expect(deleteBtn.text()).toEqual("Delete");
|
expect(deleteBtn.text()).toEqual("Delete");
|
||||||
|
|
|
@ -17,7 +17,14 @@ import {
|
||||||
FarmEventViewModel,
|
FarmEventViewModel,
|
||||||
recombine,
|
recombine,
|
||||||
destructureFarmEvent,
|
destructureFarmEvent,
|
||||||
offsetTime
|
offsetTime,
|
||||||
|
FarmEventDeleteButtonProps,
|
||||||
|
FarmEventDeleteButton,
|
||||||
|
RepeatForm,
|
||||||
|
RepeatFormProps,
|
||||||
|
StartTimeForm,
|
||||||
|
StartTimeFormProps,
|
||||||
|
FarmEventForm
|
||||||
} from "../edit_fe_form";
|
} from "../edit_fe_form";
|
||||||
import { isString, isFunction } from "lodash";
|
import { isString, isFunction } from "lodash";
|
||||||
import { repeatOptions } from "../map_state_to_props_add_edit";
|
import { repeatOptions } from "../map_state_to_props_add_edit";
|
||||||
|
@ -28,15 +35,14 @@ import {
|
||||||
buildResourceIndex
|
buildResourceIndex
|
||||||
} from "../../../__test_support__/resource_index_builder";
|
} from "../../../__test_support__/resource_index_builder";
|
||||||
import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
|
import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
|
||||||
import { clickButton } from "../../../__test_support__/helpers";
|
import { save, destroy } from "../../../api/crud";
|
||||||
import { destroy, save } from "../../../api/crud";
|
|
||||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
import { error, success } from "../../../toast/toast";
|
import { error, success } from "../../../toast/toast";
|
||||||
|
|
||||||
const mockSequence = fakeSequence();
|
const mockSequence = fakeSequence();
|
||||||
|
|
||||||
describe("<FarmEventForm/>", () => {
|
describe("<EditFEForm />", () => {
|
||||||
const props = (): EditFEProps => ({
|
const fakeProps = (): EditFEProps => ({
|
||||||
deviceTimezone: undefined,
|
deviceTimezone: undefined,
|
||||||
executableOptions: [],
|
executableOptions: [],
|
||||||
repeatOptions: [],
|
repeatOptions: [],
|
||||||
|
@ -53,34 +59,18 @@ describe("<FarmEventForm/>", () => {
|
||||||
function instance(p: EditFEProps) {
|
function instance(p: EditFEProps) {
|
||||||
return mount(<EditFEForm {...p} />).instance() as EditFEForm;
|
return mount(<EditFEForm {...p} />).instance() as EditFEForm;
|
||||||
}
|
}
|
||||||
const context = { form: new EditFEForm(props()) };
|
const context = { form: new EditFEForm(fakeProps()) };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
context.form = new EditFEForm(props());
|
context.form = new EditFEForm(fakeProps());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets defaults", () => {
|
it("sets defaults", () => {
|
||||||
expect(context.form.state.fe).toMatchObject({});
|
expect(context.form.state.fe).toMatchObject({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("determines if it is a one time event", () => {
|
|
||||||
const i = instance(props());
|
|
||||||
expect(i.repeats).toBe(false);
|
|
||||||
i.mergeState("timeUnit", "daily");
|
|
||||||
i.forceUpdate();
|
|
||||||
expect(i.repeats).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a dispatch", () => {
|
|
||||||
const p = props();
|
|
||||||
const i = instance(p);
|
|
||||||
expect(i.dispatch).toBe(p.dispatch);
|
|
||||||
i.dispatch();
|
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a view model", () => {
|
it("has a view model", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
const vm = i.viewModel;
|
const vm = i.viewModel;
|
||||||
|
@ -100,7 +90,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has an executable", () => {
|
it("has an executable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
expect(i.executableGet().value).toEqual(mockSequence.body.id);
|
expect(i.executableGet().value).toEqual(mockSequence.body.id);
|
||||||
|
@ -108,7 +98,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the executable", () => {
|
it("sets the executable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
i.executableSet({ value: "wow", label: "hey", headingId: "Sequence" });
|
i.executableSet({ value: "wow", label: "hey", headingId: "Sequence" });
|
||||||
|
@ -118,7 +108,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows proper changes to the executable", () => {
|
it("allows proper changes to the executable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.id = 0;
|
p.farmEvent.body.id = 0;
|
||||||
p.farmEvent.body.executable_type = "Sequence";
|
p.farmEvent.body.executable_type = "Sequence";
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
|
@ -128,7 +118,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't allow improper changes to the executable", () => {
|
it("doesn't allow improper changes to the executable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.id = 1;
|
p.farmEvent.body.id = 1;
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
|
@ -139,7 +129,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets executable info", () => {
|
it("gets executable info", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
const exe = i.executableGet();
|
const exe = i.executableGet();
|
||||||
|
@ -149,13 +139,12 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets a subfield of state.fe", () => {
|
it("sets a subfield of state.fe", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const i = instance(p);
|
const i = instance(p);
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
// tslint:disable-next-line:no-any
|
i.fieldSet("executable_id", "1");
|
||||||
i.fieldSet("repeat")(({ currentTarget: { value: "4" } } as any));
|
|
||||||
i.forceUpdate();
|
i.forceUpdate();
|
||||||
expect(i.state.fe.repeat).toEqual("4");
|
expect(i.state.fe.executable_id).toEqual("1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets regimen repeat to `never` as needed", () => {
|
it("sets regimen repeat to `never` as needed", () => {
|
||||||
|
@ -245,7 +234,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays success message on save: manual sync", async () => {
|
it("displays success message on save: manual sync", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.autoSyncEnabled = false;
|
p.autoSyncEnabled = false;
|
||||||
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
||||||
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
||||||
|
@ -256,7 +245,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays success message on save: auto sync", async () => {
|
it("displays success message on save: auto sync", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.autoSyncEnabled = true;
|
p.autoSyncEnabled = true;
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
const regimen = fakeRegimen();
|
const regimen = fakeRegimen();
|
||||||
|
@ -273,7 +262,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns about missed regimen items", async () => {
|
it("warns about missed regimen items", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
const regimen = fakeRegimen();
|
const regimen = fakeRegimen();
|
||||||
regimen.body.regimen_items = [
|
regimen.body.regimen_items = [
|
||||||
|
@ -293,7 +282,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends toast with regimen start time", async () => {
|
it("sends toast with regimen start time", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
const regimen = fakeRegimen();
|
const regimen = fakeRegimen();
|
||||||
regimen.body.regimen_items = [{ sequence_id: -1, time_offset: 1000000000 }];
|
regimen.body.regimen_items = [{ sequence_id: -1, time_offset: 1000000000 }];
|
||||||
|
@ -308,7 +297,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends toast with next sequence run time", async () => {
|
it("sends toast with next sequence run time", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.executable_type = "Sequence";
|
p.farmEvent.body.executable_type = "Sequence";
|
||||||
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
||||||
p.farmEvent.body.end_time = "2017-06-22T06:00:00.000Z";
|
p.farmEvent.body.end_time = "2017-06-22T06:00:00.000Z";
|
||||||
|
@ -328,7 +317,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it("displays error message on save (add): start time has passed", () => {
|
it("displays error message on save (add): start time has passed", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.title = "add";
|
p.title = "add";
|
||||||
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
||||||
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
||||||
|
@ -338,7 +327,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays error message on edit: start time has passed", () => {
|
it("displays error message on edit: start time has passed", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.title = "edit";
|
p.title = "edit";
|
||||||
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
||||||
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
||||||
|
@ -349,7 +338,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays error message on save: no items", async () => {
|
it("displays error message on save: no items", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.shouldDisplay = () => true;
|
p.shouldDisplay = () => true;
|
||||||
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-05-22T05:00:00.000Z";
|
||||||
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
p.farmEvent.body.end_time = "2017-05-22T06:00:00.000Z";
|
||||||
|
@ -360,7 +349,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays error message on save: save error", async () => {
|
it("displays error message on save: save error", async () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.dispatch = jest.fn()
|
p.dispatch = jest.fn()
|
||||||
.mockResolvedValueOnce("")
|
.mockResolvedValueOnce("")
|
||||||
.mockRejectedValueOnce("error");
|
.mockRejectedValueOnce("error");
|
||||||
|
@ -374,7 +363,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows start time: edit with unsupported OS", () => {
|
it("allows start time: edit with unsupported OS", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.shouldDisplay = () => false;
|
p.shouldDisplay = () => false;
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
||||||
|
@ -385,7 +374,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows start time: add with supported OS", () => {
|
it("allows start time: add with supported OS", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.title = "add";
|
p.title = "add";
|
||||||
p.shouldDisplay = () => true;
|
p.shouldDisplay = () => true;
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
p.farmEvent.body.executable_type = "Regimen";
|
||||||
|
@ -396,7 +385,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects start time: add sequence event", () => {
|
it("rejects start time: add sequence event", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.title = "add";
|
p.title = "add";
|
||||||
p.farmEvent.body.executable_type = "Sequence";
|
p.farmEvent.body.executable_type = "Sequence";
|
||||||
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
||||||
|
@ -406,7 +395,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows start time: edit sequence event", () => {
|
it("allows start time: edit sequence event", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.farmEvent.body.executable_type = "Sequence";
|
p.farmEvent.body.executable_type = "Sequence";
|
||||||
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
||||||
const fakeNow = moment("2017-06-01T02:00:00.000Z");
|
const fakeNow = moment("2017-06-01T02:00:00.000Z");
|
||||||
|
@ -416,7 +405,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows start time in the future", () => {
|
it("allows start time in the future", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.title = "add";
|
p.title = "add";
|
||||||
p.farmEvent.body.executable_type = "Sequence";
|
p.farmEvent.body.executable_type = "Sequence";
|
||||||
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
p.farmEvent.body.start_time = "2017-06-01T01:00:00.000Z";
|
||||||
|
@ -426,7 +415,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("edits a variable", () => {
|
it("edits a variable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const oldVariable: ParameterApplication = {
|
const oldVariable: ParameterApplication = {
|
||||||
kind: "parameter_application",
|
kind: "parameter_application",
|
||||||
args: {
|
args: {
|
||||||
|
@ -455,7 +444,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("saves an updated variable", () => {
|
it("saves an updated variable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const oldVariable: ParameterApplication = {
|
const oldVariable: ParameterApplication = {
|
||||||
kind: "parameter_application",
|
kind: "parameter_application",
|
||||||
args: {
|
args: {
|
||||||
|
@ -481,7 +470,7 @@ describe("<FarmEventForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("saves the current variable", () => {
|
it("saves the current variable", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const sequence = fakeSequence();
|
const sequence = fakeSequence();
|
||||||
p.findExecutable = () => sequence;
|
p.findExecutable = () => sequence;
|
||||||
const plant = fakePlant();
|
const plant = fakePlant();
|
||||||
|
@ -504,44 +493,11 @@ describe("<FarmEventForm/>", () => {
|
||||||
expect(inst.updatedFarmEvent.body).toEqual([oldVariable]);
|
expect(inst.updatedFarmEvent.body).toEqual([oldVariable]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deletes a farmEvent", async () => {
|
it("shows error message upon farm event save", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.dispatch = jest.fn(() => Promise.resolve());
|
const wrapper = shallow(<EditFEForm {...p} />);
|
||||||
const inst = instance(p);
|
wrapper.find(FarmEventForm).simulate("save");
|
||||||
const wrapper = shallow(<inst.FarmEventDeleteButton />);
|
expect(error).toHaveBeenCalled();
|
||||||
clickButton(wrapper, 0, "delete");
|
|
||||||
await expect(destroy).toHaveBeenCalledWith(p.farmEvent.uuid);
|
|
||||||
expect(history.push).toHaveBeenCalledWith("/app/designer/events");
|
|
||||||
expect(success).toHaveBeenCalledWith("Deleted event.", "Deleted");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets repeat", () => {
|
|
||||||
const p = props();
|
|
||||||
p.dispatch = jest.fn(() => Promise.resolve());
|
|
||||||
const e = {
|
|
||||||
currentTarget: { checked: true }
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>;
|
|
||||||
const inst = instance(p);
|
|
||||||
inst.toggleRepeat(e);
|
|
||||||
expect(inst.state).toEqual({
|
|
||||||
fe: { timeUnit: "daily" },
|
|
||||||
specialStatusLocal: SpecialStatus.DIRTY
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets repeat: regimen", () => {
|
|
||||||
const p = props();
|
|
||||||
p.farmEvent.body.executable_type = "Regimen";
|
|
||||||
p.dispatch = jest.fn(() => Promise.resolve());
|
|
||||||
const e = {
|
|
||||||
currentTarget: { checked: true }
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>;
|
|
||||||
const inst = instance(p);
|
|
||||||
inst.toggleRepeat(e);
|
|
||||||
expect(inst.state).toEqual({
|
|
||||||
fe: { timeUnit: "never" },
|
|
||||||
specialStatusLocal: SpecialStatus.DIRTY
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -557,3 +513,75 @@ describe("destructureFarmEvent", () => {
|
||||||
expect(endTime).toBe("23:32");
|
expect(endTime).toBe("23:32");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("<StartTimeForm />", () => {
|
||||||
|
const fakeProps = (): StartTimeFormProps => ({
|
||||||
|
isRegimen: false,
|
||||||
|
fieldGet: jest.fn(),
|
||||||
|
fieldSet: jest.fn(),
|
||||||
|
timeSettings: fakeTimeSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes start date", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<StartTimeForm {...p} />);
|
||||||
|
wrapper.find("BlurableInput").first().simulate("commit", {
|
||||||
|
currentTarget: { value: "2017-07-26" }
|
||||||
|
});
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith("startDate", "2017-07-26");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes start time", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<StartTimeForm {...p} />);
|
||||||
|
wrapper.find("EventTimePicker").simulate("commit", {
|
||||||
|
currentTarget: { value: "08:57" }
|
||||||
|
});
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith("startTime", "08:57");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<RepeatForm />", () => {
|
||||||
|
const fakeProps = (): RepeatFormProps => ({
|
||||||
|
isRegimen: false,
|
||||||
|
fieldGet: jest.fn(key =>
|
||||||
|
"" + ({ endDate: "2017-07-26" } as FarmEventViewModel)[key]),
|
||||||
|
fieldSet: jest.fn(),
|
||||||
|
timeSettings: fakeTimeSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles repeat on", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<RepeatForm {...p} />);
|
||||||
|
wrapper.find("input").first().simulate("change", {
|
||||||
|
currentTarget: { checked: true }
|
||||||
|
});
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith("timeUnit", "daily");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles repeat off", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<RepeatForm {...p} />);
|
||||||
|
wrapper.find("input").first().simulate("change", {
|
||||||
|
currentTarget: { checked: false }
|
||||||
|
});
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith("timeUnit", "never");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<FarmEventDeleteButton />", () => {
|
||||||
|
const fakeProps = (): FarmEventDeleteButtonProps => ({
|
||||||
|
hidden: false,
|
||||||
|
farmEvent: fakeFarmEvent("Sequence", 1),
|
||||||
|
dispatch: jest.fn(() => Promise.resolve()),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes farm event", async () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<FarmEventDeleteButton {...p} />);
|
||||||
|
await wrapper.find("button").simulate("click");
|
||||||
|
expect(destroy).toHaveBeenCalledWith(p.farmEvent.uuid);
|
||||||
|
expect(history.push).toHaveBeenCalledWith("/app/designer/events");
|
||||||
|
expect(success).toHaveBeenCalledWith("Deleted event.", "Deleted");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { RepeatFormProps, FarmEventRepeatForm } from "../farm_event_repeat_form";
|
import {
|
||||||
import { betterMerge } from "../../../util";
|
FarmEventRepeatFormProps, FarmEventRepeatForm
|
||||||
|
} from "../farm_event_repeat_form";
|
||||||
import { shallow, ShallowWrapper, render } from "enzyme";
|
import { shallow, ShallowWrapper, render } from "enzyme";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
|
|
||||||
const DEFAULTS: RepeatFormProps = {
|
const fakeProps = (): FarmEventRepeatFormProps => ({
|
||||||
disabled: false,
|
disabled: false,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
onChange: jest.fn(),
|
fieldSet: jest.fn(),
|
||||||
timeUnit: "daily",
|
timeUnit: "daily",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
endDate: "2017-07-26",
|
endDate: "2017-07-26",
|
||||||
endTime: "08:57",
|
endTime: "08:57",
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
};
|
});
|
||||||
|
|
||||||
enum Selectors {
|
enum Selectors {
|
||||||
REPEAT = "BlurableInput[name=\"repeat\"]",
|
REPEAT = "BlurableInput[name=\"repeat\"]",
|
||||||
|
@ -23,10 +24,6 @@ enum Selectors {
|
||||||
TIME_UNIT = "FBSelect"
|
TIME_UNIT = "FBSelect"
|
||||||
}
|
}
|
||||||
|
|
||||||
function props(i?: Partial<RepeatFormProps>): RepeatFormProps {
|
|
||||||
return betterMerge(DEFAULTS, i || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formVal(el: ShallowWrapper<{}, {}>, query: string) {
|
function formVal(el: ShallowWrapper<{}, {}>, query: string) {
|
||||||
return getProp(el, query, "value");
|
return getProp(el, query, "value");
|
||||||
}
|
}
|
||||||
|
@ -37,8 +34,8 @@ function getProp(el: ShallowWrapper<{}, {}>, query: string, prop: string) {
|
||||||
|
|
||||||
describe("<FarmEventRepeatForm/>", () => {
|
describe("<FarmEventRepeatForm/>", () => {
|
||||||
it("shows proper values", () => {
|
it("shows proper values", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const el = shallow<RepeatFormProps>(<FarmEventRepeatForm {...p} />);
|
const el = shallow<FarmEventRepeatFormProps>(<FarmEventRepeatForm {...p} />);
|
||||||
expect(formVal(el, Selectors.REPEAT)).toEqual(p.repeat);
|
expect(formVal(el, Selectors.REPEAT)).toEqual(p.repeat);
|
||||||
expect(formVal(el, Selectors.END_DATE)).toEqual(p.endDate);
|
expect(formVal(el, Selectors.END_DATE)).toEqual(p.endDate);
|
||||||
expect(formVal(el, Selectors.END_TIME)).toEqual(p.endTime);
|
expect(formVal(el, Selectors.END_TIME)).toEqual(p.endTime);
|
||||||
|
@ -47,7 +44,7 @@ describe("<FarmEventRepeatForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults to `daily` when a bad input it passed", () => {
|
it("defaults to `daily` when a bad input it passed", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.timeUnit = "never";
|
p.timeUnit = "never";
|
||||||
const el = shallow(<FarmEventRepeatForm {...p} />);
|
const el = shallow(<FarmEventRepeatForm {...p} />);
|
||||||
expect(formVal(el, Selectors.REPEAT)).toEqual(p.repeat);
|
expect(formVal(el, Selectors.REPEAT)).toEqual(p.repeat);
|
||||||
|
@ -55,7 +52,7 @@ describe("<FarmEventRepeatForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disables all inputs via the `disabled` prop", () => {
|
it("disables all inputs via the `disabled` prop", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.disabled = true;
|
p.disabled = true;
|
||||||
const el = shallow(<FarmEventRepeatForm {...p} />);
|
const el = shallow(<FarmEventRepeatForm {...p} />);
|
||||||
expect(getProp(el, Selectors.END_DATE, "disabled")).toBeTruthy();
|
expect(getProp(el, Selectors.END_DATE, "disabled")).toBeTruthy();
|
||||||
|
@ -65,9 +62,37 @@ describe("<FarmEventRepeatForm/>", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("hides", () => {
|
it("hides", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.hidden = true;
|
p.hidden = true;
|
||||||
const el = render(<FarmEventRepeatForm {...p} />);
|
const el = render(<FarmEventRepeatForm {...p} />);
|
||||||
expect(el.text()).toEqual("");
|
expect(el.text()).toEqual("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const testBlurable = (input: string, field: string, value: string) => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<FarmEventRepeatForm {...p} />);
|
||||||
|
wrapper.find(input).simulate("commit", {
|
||||||
|
currentTarget: { value }
|
||||||
|
});
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith(field, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("changes repeat frequency", () => {
|
||||||
|
testBlurable(Selectors.REPEAT, "repeat", "1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes time unit", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<FarmEventRepeatForm {...p} />);
|
||||||
|
wrapper.find(Selectors.TIME_UNIT).simulate("change", { value: "daily" });
|
||||||
|
expect(p.fieldSet).toHaveBeenCalledWith("timeUnit", "daily");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes end date", () => {
|
||||||
|
testBlurable(Selectors.END_DATE, "endDate", "2017-07-26");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes end time", () => {
|
||||||
|
testBlurable(Selectors.END_TIME, "endTime", "08:57");
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -5,7 +5,6 @@ import {
|
||||||
} from "../../../__test_support__/farm_event_calendar_support";
|
} from "../../../__test_support__/farm_event_calendar_support";
|
||||||
import { render, shallow, mount } from "enzyme";
|
import { render, shallow, mount } from "enzyme";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { Content } from "../../../constants";
|
|
||||||
import { defensiveClone } from "../../../util";
|
import { defensiveClone } from "../../../util";
|
||||||
import { FarmEventProps } from "../../interfaces";
|
import { FarmEventProps } from "../../interfaces";
|
||||||
|
|
||||||
|
@ -29,15 +28,6 @@ describe("<PureFarmEvents/>", () => {
|
||||||
expect(rows[2]).toEqual("02:00pm");
|
expect(rows[2]).toEqual("02:00pm");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns about unset timezones", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
p.timezoneIsSet = false;
|
|
||||||
const results = render(<PureFarmEvents {...p} />);
|
|
||||||
const txt = results.text();
|
|
||||||
expect(txt).toContain(Content.SET_TIMEZONE_HEADER);
|
|
||||||
expect(txt).toContain(Content.SET_TIMEZONE_BODY);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders FarmEvent lacking a subheading", () => {
|
it("renders FarmEvent lacking a subheading", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const row = [defensiveClone(calendarRows[0])];
|
const row = [defensiveClone(calendarRows[0])];
|
||||||
|
|
|
@ -1,36 +1,53 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { mapStateToPropsAddEdit, } from "./map_state_to_props_add_edit";
|
import {
|
||||||
|
mapStateToPropsAddEdit, formatDate, formatTime,
|
||||||
|
} from "./map_state_to_props_add_edit";
|
||||||
import { init, destroy } from "../../api/crud";
|
import { init, destroy } from "../../api/crud";
|
||||||
import { EditFEForm } from "./edit_fe_form";
|
import {
|
||||||
import { betterCompact } from "../../util";
|
EditFEForm, FarmEventForm, FarmEventViewModel, NEVER
|
||||||
|
} from "./edit_fe_form";
|
||||||
|
import { betterCompact, betterMerge } from "../../util";
|
||||||
import { entries } from "../../resources/util";
|
import { entries } from "../../resources/util";
|
||||||
import {
|
import {
|
||||||
AddEditFarmEventProps,
|
AddEditFarmEventProps,
|
||||||
TaggedExecutable
|
TaggedExecutable
|
||||||
} from "../interfaces";
|
} from "../interfaces";
|
||||||
import { ExecutableType } from "farmbot/dist/resources/api_resources";
|
import { ExecutableType } from "farmbot/dist/resources/api_resources";
|
||||||
import { Link } from "../../link";
|
|
||||||
import {
|
import {
|
||||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||||
} from "../designer_panel";
|
} from "../designer_panel";
|
||||||
import { variableList } from "../../sequences/locals_list/variable_support";
|
import { variableList } from "../../sequences/locals_list/variable_support";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { Panel } from "../panel_header";
|
import { Panel } from "../panel_header";
|
||||||
|
import { SpecialStatus } from "farmbot";
|
||||||
|
import { destroyOK } from "../../resources/actions";
|
||||||
|
import { Content } from "../../constants";
|
||||||
|
import { error } from "../../toast/toast";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
temporaryValues: Partial<FarmEventViewModel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RawAddFarmEvent
|
export class RawAddFarmEvent
|
||||||
extends React.Component<AddEditFarmEventProps, Partial<State>> {
|
extends React.Component<AddEditFarmEventProps, State> {
|
||||||
|
temporaryValueDefaults = () => {
|
||||||
constructor(props: AddEditFarmEventProps) {
|
const now = new Date();
|
||||||
super(props);
|
const later = new Date(now.getTime() + 60000);
|
||||||
this.state = {};
|
return {
|
||||||
|
startDate: formatDate(now.toString(), this.props.timeSettings),
|
||||||
|
startTime: formatTime(now.toString(), this.props.timeSettings),
|
||||||
|
endDate: formatDate(now.toString(), this.props.timeSettings),
|
||||||
|
endTime: formatTime(later.toString(), this.props.timeSettings),
|
||||||
|
repeat: "1",
|
||||||
|
timeUnit: NEVER,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state: State = { uuid: "", temporaryValues: this.temporaryValueDefaults() };
|
||||||
|
|
||||||
get sequences() { return betterCompact(entries(this.props.sequencesById)); }
|
get sequences() { return betterCompact(entries(this.props.sequencesById)); }
|
||||||
|
|
||||||
get regimens() { return betterCompact(entries(this.props.regimensById)); }
|
get regimens() { return betterCompact(entries(this.props.regimensById)); }
|
||||||
|
@ -71,55 +88,51 @@ export class RawAddFarmEvent
|
||||||
if (fe && unsaved) { this.props.dispatch(destroy(fe.uuid, true)); }
|
if (fe && unsaved) { this.props.dispatch(destroy(fe.uuid, true)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** No executables. Can't load form. */
|
getField = (field: keyof State["temporaryValues"]): string =>
|
||||||
none() {
|
"" + this.state.temporaryValues[field];
|
||||||
return <p>
|
setField = (field: keyof State["temporaryValues"], value: string) =>
|
||||||
{t("You haven't made any regimens or sequences yet. Please create a ")}
|
this.setState(betterMerge(this.state, {
|
||||||
<Link to="/app/sequences">{t("sequence")}</Link> {t(" or ")}
|
temporaryValues: { [field]: value }
|
||||||
<Link to="/app/regimens">{t("regimen")}</Link> {t(" first.")}
|
}))
|
||||||
</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** User has executables to create FarmEvents with, has not loaded yet. */
|
|
||||||
loading() {
|
|
||||||
return <p>{t("Loading")}...</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholderTemplate(children: React.ReactChild | React.ReactChild[]) {
|
|
||||||
return <DesignerPanel panelName={"add-farm-event"} panel={Panel.FarmEvents}>
|
|
||||||
<DesignerPanelHeader
|
|
||||||
panelName={"add-farm-event"}
|
|
||||||
panel={Panel.FarmEvents}
|
|
||||||
title={t("No Executables")} />
|
|
||||||
<DesignerPanelContent panelName={"add-farm-event"}>
|
|
||||||
<label>
|
|
||||||
{children}
|
|
||||||
</label>
|
|
||||||
</DesignerPanelContent>
|
|
||||||
</DesignerPanel>;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { uuid } = this.state;
|
const farmEvent = this.props.findFarmEventByUuid(this.state.uuid);
|
||||||
const fe = this.props.findFarmEventByUuid(uuid);
|
const panelName = "add-farm-event";
|
||||||
if (fe) {
|
return <DesignerPanel panelName={panelName} panel={Panel.FarmEvents}>
|
||||||
return <EditFEForm
|
<DesignerPanelHeader
|
||||||
farmEvent={fe}
|
panelName={panelName}
|
||||||
deviceTimezone={this.props.deviceTimezone}
|
panel={Panel.FarmEvents}
|
||||||
repeatOptions={this.props.repeatOptions}
|
|
||||||
executableOptions={this.props.executableOptions}
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
findExecutable={this.props.findExecutable}
|
|
||||||
title={t("Add Event")}
|
title={t("Add Event")}
|
||||||
timeSettings={this.props.timeSettings}
|
onBack={(farmEvent && !farmEvent.body.id)
|
||||||
autoSyncEnabled={this.props.autoSyncEnabled}
|
? () => this.props.dispatch(destroyOK(farmEvent))
|
||||||
resources={this.props.resources}
|
: undefined} />
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
<DesignerPanelContent panelName={panelName}>
|
||||||
/>;
|
{farmEvent
|
||||||
} else {
|
? <EditFEForm
|
||||||
return this
|
farmEvent={farmEvent}
|
||||||
.placeholderTemplate(this.executable ? this.loading() : this.none());
|
deviceTimezone={this.props.deviceTimezone}
|
||||||
}
|
repeatOptions={this.props.repeatOptions}
|
||||||
|
executableOptions={this.props.executableOptions}
|
||||||
|
dispatch={this.props.dispatch}
|
||||||
|
findExecutable={this.props.findExecutable}
|
||||||
|
title={t("Add Event")}
|
||||||
|
timeSettings={this.props.timeSettings}
|
||||||
|
autoSyncEnabled={this.props.autoSyncEnabled}
|
||||||
|
resources={this.props.resources}
|
||||||
|
shouldDisplay={this.props.shouldDisplay} />
|
||||||
|
: <FarmEventForm
|
||||||
|
isRegimen={false}
|
||||||
|
fieldGet={this.getField}
|
||||||
|
fieldSet={this.setField}
|
||||||
|
timeSettings={this.props.timeSettings}
|
||||||
|
executableOptions={[]}
|
||||||
|
executableSet={() => { }}
|
||||||
|
executableGet={() => undefined}
|
||||||
|
dispatch={this.props.dispatch}
|
||||||
|
specialStatus={SpecialStatus.DIRTY}
|
||||||
|
onSave={() => error(t(Content.MISSING_EXECUTABLE))} />}
|
||||||
|
</DesignerPanelContent>
|
||||||
|
</DesignerPanel>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import moment from "moment";
|
||||||
import { success, error } from "../../toast/toast";
|
import { success, error } from "../../toast/toast";
|
||||||
import {
|
import {
|
||||||
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen,
|
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen,
|
||||||
ParameterApplication
|
ParameterApplication,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { ExecutableQuery } from "../interfaces";
|
import { ExecutableQuery } from "../interfaces";
|
||||||
import { formatTime, formatDate } from "./map_state_to_props_add_edit";
|
import { formatTime, formatDate } from "./map_state_to_props_add_edit";
|
||||||
|
@ -12,18 +12,17 @@ import {
|
||||||
Col, Row,
|
Col, Row,
|
||||||
SaveBtn,
|
SaveBtn,
|
||||||
FBSelect,
|
FBSelect,
|
||||||
DropDownItem
|
DropDownItem,
|
||||||
|
Help,
|
||||||
} from "../../ui";
|
} from "../../ui";
|
||||||
import { destroy, save, overwrite } from "../../api/crud";
|
import { destroy, save, overwrite } from "../../api/crud";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
// TIL: https://stackoverflow.com/a/24900248/1064917
|
|
||||||
import { betterMerge, parseIntInput } from "../../util";
|
import { betterMerge, parseIntInput } from "../../util";
|
||||||
import { maybeWarnAboutMissedTasks } from "./util";
|
import { maybeWarnAboutMissedTasks } from "./util";
|
||||||
import { FarmEventRepeatForm } from "./farm_event_repeat_form";
|
import { FarmEventRepeatForm } from "./farm_event_repeat_form";
|
||||||
import { scheduleForFarmEvent } from "./calendar/scheduler";
|
import { scheduleForFarmEvent } from "./calendar/scheduler";
|
||||||
import { executableType } from "../util";
|
import { executableType } from "../util";
|
||||||
import { Content } from "../../constants";
|
import { Content } from "../../constants";
|
||||||
import { destroyOK } from "../../resources/actions";
|
|
||||||
import { EventTimePicker } from "./event_time_picker";
|
import { EventTimePicker } from "./event_time_picker";
|
||||||
import { TzWarning } from "./tz_warning";
|
import { TzWarning } from "./tz_warning";
|
||||||
import { nextRegItemTimes } from "./map_state_to_props";
|
import { nextRegItemTimes } from "./map_state_to_props";
|
||||||
|
@ -31,9 +30,6 @@ import { first } from "lodash";
|
||||||
import {
|
import {
|
||||||
TimeUnit, ExecutableType, FarmEvent
|
TimeUnit, ExecutableType, FarmEvent
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import {
|
|
||||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
|
||||||
} from "../designer_panel";
|
|
||||||
import { LocalsList } from "../../sequences/locals_list/locals_list";
|
import { LocalsList } from "../../sequences/locals_list/locals_list";
|
||||||
import { ResourceIndex } from "../../resources/interfaces";
|
import { ResourceIndex } from "../../resources/interfaces";
|
||||||
import { ShouldDisplay } from "../../devices/interfaces";
|
import { ShouldDisplay } from "../../devices/interfaces";
|
||||||
|
@ -41,13 +37,11 @@ import {
|
||||||
addOrEditParamApps, variableList, getRegimenVariableData
|
addOrEditParamApps, variableList, getRegimenVariableData
|
||||||
} from "../../sequences/locals_list/variable_support";
|
} from "../../sequences/locals_list/variable_support";
|
||||||
import {
|
import {
|
||||||
AllowedVariableNodes
|
AllowedVariableNodes,
|
||||||
} from "../../sequences/locals_list/locals_list_support";
|
} from "../../sequences/locals_list/locals_list_support";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../interfaces";
|
import { TimeSettings } from "../../interfaces";
|
||||||
import { Panel } from "../panel_header";
|
|
||||||
|
|
||||||
type FormEvent = React.SyntheticEvent<HTMLInputElement>;
|
|
||||||
export const NEVER: TimeUnit = "never";
|
export const NEVER: TimeUnit = "never";
|
||||||
/** Separate each of the form fields into their own interface. Recombined later
|
/** Separate each of the form fields into their own interface. Recombined later
|
||||||
* on save.
|
* on save.
|
||||||
|
@ -66,6 +60,8 @@ export interface FarmEventViewModel {
|
||||||
body?: ParameterApplication[];
|
body?: ParameterApplication[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FarmEventViewModelKey = keyof FarmEventViewModel;
|
||||||
|
|
||||||
/** Breaks up a TaggedFarmEvent into a structure that can easily be used
|
/** Breaks up a TaggedFarmEvent into a structure that can easily be used
|
||||||
* by the edit form.
|
* by the edit form.
|
||||||
* USE CASE EXAMPLE: We have a "date" and "time" field that are created from
|
* USE CASE EXAMPLE: We have a "date" and "time" field that are created from
|
||||||
|
@ -148,7 +144,7 @@ export interface EditFEProps {
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
export interface EditFEFormState {
|
||||||
/**
|
/**
|
||||||
* Hold a partial FarmEvent locally containing only updates made by the form.
|
* Hold a partial FarmEvent locally containing only updates made by the form.
|
||||||
*/
|
*/
|
||||||
|
@ -162,12 +158,8 @@ interface State {
|
||||||
specialStatusLocal: SpecialStatus;
|
specialStatusLocal: SpecialStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditFEForm extends React.Component<EditFEProps, State> {
|
export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
|
||||||
state: State = { fe: {}, specialStatusLocal: SpecialStatus.SAVED };
|
state: EditFEFormState = { fe: {}, specialStatusLocal: SpecialStatus.SAVED };
|
||||||
|
|
||||||
get repeats() { return this.fieldGet("timeUnit") !== NEVER; }
|
|
||||||
|
|
||||||
get dispatch() { return this.props.dispatch; }
|
|
||||||
|
|
||||||
/** API data for the FarmEvent to which form updates can be applied. */
|
/** API data for the FarmEvent to which form updates can be applied. */
|
||||||
get viewModel() {
|
get viewModel() {
|
||||||
|
@ -240,7 +232,7 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
|
||||||
const { uuid } = this.props.findExecutable(
|
const { uuid } = this.props.findExecutable(
|
||||||
next_executable_type, parseInt("" + ddi.value));
|
next_executable_type, parseInt("" + ddi.value));
|
||||||
const varData = this.props.resources.sequenceMetas[uuid];
|
const varData = this.props.resources.sequenceMetas[uuid];
|
||||||
const update: State = {
|
const update: EditFEFormState = {
|
||||||
fe: {
|
fe: {
|
||||||
executable_type: next_executable_type,
|
executable_type: next_executable_type,
|
||||||
executable_id: (ddi.value || "").toString(),
|
executable_id: (ddi.value || "").toString(),
|
||||||
|
@ -263,28 +255,15 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldSet = (name: keyof State["fe"]) => (e: FormEvent) => {
|
fieldSet = (name: FarmEventViewModelKey, value: string) =>
|
||||||
|
// A merge is required to not overwrite `fe`.
|
||||||
this.setState(betterMerge(this.state, {
|
this.setState(betterMerge(this.state, {
|
||||||
fe: { [name]: e.currentTarget.value },
|
fe: { [name]: value },
|
||||||
specialStatusLocal: SpecialStatus.DIRTY
|
specialStatusLocal: SpecialStatus.DIRTY
|
||||||
}));
|
}))
|
||||||
}
|
|
||||||
|
|
||||||
fieldGet = (name: keyof State["fe"]): string => {
|
fieldGet = (name: FarmEventViewModelKey): string =>
|
||||||
return (this.state.fe[name] || this.viewModel[name] || "").toString();
|
(this.state.fe[name] || this.viewModel[name] || "").toString()
|
||||||
}
|
|
||||||
|
|
||||||
mergeState = (k: keyof FarmEventViewModel, v: string) => {
|
|
||||||
this.setState(betterMerge(this.state, {
|
|
||||||
fe: { [k]: v },
|
|
||||||
specialStatusLocal: SpecialStatus.DIRTY
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleRepeat = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const { checked } = e.currentTarget;
|
|
||||||
this.mergeState("timeUnit", (!checked || this.isReg) ? "never" : "daily");
|
|
||||||
};
|
|
||||||
|
|
||||||
nextItemTime = (fe: FarmEvent, now: moment.Moment
|
nextItemTime = (fe: FarmEvent, now: moment.Moment
|
||||||
): moment.Moment | undefined => {
|
): moment.Moment | undefined => {
|
||||||
|
@ -362,11 +341,12 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
|
||||||
if (!this.nextItemTime(this.updatedFarmEvent, now)) {
|
if (!this.nextItemTime(this.updatedFarmEvent, now)) {
|
||||||
return nothingToRunWarning();
|
return nothingToRunWarning();
|
||||||
}
|
}
|
||||||
this.dispatch(overwrite(this.props.farmEvent, this.updatedFarmEvent));
|
const { dispatch } = this.props;
|
||||||
this.dispatch(save(this.props.farmEvent.uuid))
|
dispatch(overwrite(this.props.farmEvent, this.updatedFarmEvent));
|
||||||
|
dispatch(save(this.props.farmEvent.uuid))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({ specialStatusLocal: SpecialStatus.SAVED });
|
this.setState({ specialStatusLocal: SpecialStatus.SAVED });
|
||||||
this.dispatch(maybeWarnAboutMissedTasks(this.props.farmEvent,
|
dispatch(maybeWarnAboutMissedTasks(this.props.farmEvent,
|
||||||
() => alert(t(Content.REGIMEN_TODAY_SKIPPED_ITEM_RISK)), now));
|
() => alert(t(Content.REGIMEN_TODAY_SKIPPED_ITEM_RISK)), now));
|
||||||
this.nextRunTimeActions(now);
|
this.nextRunTimeActions(now);
|
||||||
history.push("/app/designer/events");
|
history.push("/app/designer/events");
|
||||||
|
@ -377,123 +357,184 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
StartTimeForm = () => {
|
|
||||||
const forceMidnight = this.isReg;
|
|
||||||
return <div>
|
|
||||||
<label>
|
|
||||||
{t("Starts")}
|
|
||||||
</label>
|
|
||||||
<Row>
|
|
||||||
<Col xs={6}>
|
|
||||||
<BlurableInput
|
|
||||||
type="date"
|
|
||||||
className="add-event-start-date"
|
|
||||||
name="start_date"
|
|
||||||
value={this.fieldGet("startDate")}
|
|
||||||
onCommit={this.fieldSet("startDate")} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={6}>
|
|
||||||
<EventTimePicker
|
|
||||||
className="add-event-start-time"
|
|
||||||
name="start_time"
|
|
||||||
timeSettings={this.props.timeSettings}
|
|
||||||
value={this.fieldGet("startTime")}
|
|
||||||
onCommit={this.fieldSet("startTime")}
|
|
||||||
disabled={forceMidnight}
|
|
||||||
hidden={forceMidnight} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
RepeatCheckbox = ({ allowRepeat }: { allowRepeat: boolean }) =>
|
|
||||||
!this.isReg ?
|
|
||||||
<label>
|
|
||||||
<input type="checkbox"
|
|
||||||
onChange={this.toggleRepeat}
|
|
||||||
disabled={this.isReg}
|
|
||||||
checked={allowRepeat} />
|
|
||||||
{t("Repeats?")}
|
|
||||||
</label> : <div />
|
|
||||||
|
|
||||||
dateCheck = (): string | undefined => {
|
|
||||||
const startDate = this.fieldGet("startDate");
|
|
||||||
const endDate = this.fieldGet("endDate");
|
|
||||||
if (!moment(endDate).isSameOrAfter(moment(startDate))) {
|
|
||||||
return t("End date must not be before start date.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeCheck = (): string | undefined => {
|
|
||||||
const startDate = this.fieldGet("startDate");
|
|
||||||
const startTime = this.fieldGet("startTime");
|
|
||||||
const endDate = this.fieldGet("endDate");
|
|
||||||
const endTime = this.fieldGet("endTime");
|
|
||||||
const start = offsetTime(startDate, startTime, this.props.timeSettings);
|
|
||||||
const end = offsetTime(endDate, endTime, this.props.timeSettings);
|
|
||||||
if (moment(start).isSameOrAfter(moment(end))) {
|
|
||||||
return t("End time must be after start time.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RepeatForm = () => {
|
|
||||||
const allowRepeat = !this.isReg && this.repeats;
|
|
||||||
return <div>
|
|
||||||
<this.RepeatCheckbox allowRepeat={allowRepeat} />
|
|
||||||
<FarmEventRepeatForm
|
|
||||||
timeSettings={this.props.timeSettings}
|
|
||||||
disabled={!allowRepeat}
|
|
||||||
hidden={!allowRepeat}
|
|
||||||
onChange={this.mergeState}
|
|
||||||
timeUnit={this.fieldGet("timeUnit") as TimeUnit}
|
|
||||||
repeat={this.fieldGet("repeat")}
|
|
||||||
endDate={this.fieldGet("endDate")}
|
|
||||||
endTime={this.fieldGet("endTime")}
|
|
||||||
dateError={this.dateCheck()}
|
|
||||||
timeError={this.timeCheck()} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
FarmEventDeleteButton = () =>
|
|
||||||
<button className="fb-button red" hidden={!this.props.deleteBtn}
|
|
||||||
onClick={() => {
|
|
||||||
this.dispatch(destroy(this.props.farmEvent.uuid))
|
|
||||||
.then(() => {
|
|
||||||
history.push("/app/designer/events");
|
|
||||||
success(t("Deleted event."), t("Deleted"));
|
|
||||||
});
|
|
||||||
}}>
|
|
||||||
{t("Delete")}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { farmEvent } = this.props;
|
const { farmEvent } = this.props;
|
||||||
return <DesignerPanel panelName={"add-farm-event"} panel={Panel.FarmEvents}>
|
return <div className="edit-farm-event-form">
|
||||||
<DesignerPanelHeader
|
<FarmEventForm
|
||||||
panelName={"add-farm-event"}
|
isRegimen={this.isReg}
|
||||||
panel={Panel.FarmEvents}
|
fieldGet={this.fieldGet}
|
||||||
title={this.props.title}
|
fieldSet={this.fieldSet}
|
||||||
onBack={!farmEvent.body.id ? () =>
|
timeSettings={this.props.timeSettings}
|
||||||
// Throw out unsaved farmevents.
|
executableOptions={this.props.executableOptions}
|
||||||
this.props.dispatch(destroyOK(farmEvent))
|
executableSet={this.executableSet}
|
||||||
: undefined} />
|
executableGet={this.executableGet}
|
||||||
<DesignerPanelContent panelName={"add-farm-event"}>
|
dispatch={this.props.dispatch}
|
||||||
<label>
|
specialStatus={farmEvent.specialStatus || this.state.specialStatusLocal}
|
||||||
{t("Sequence or Regimen")}
|
onSave={() => this.commitViewModel()}>
|
||||||
</label>
|
|
||||||
<FBSelect
|
|
||||||
list={this.props.executableOptions}
|
|
||||||
onChange={this.executableSet}
|
|
||||||
selectedItem={this.executableGet()} />
|
|
||||||
<this.LocalsList />
|
<this.LocalsList />
|
||||||
<this.StartTimeForm />
|
</FarmEventForm>
|
||||||
<this.RepeatForm />
|
<FarmEventDeleteButton
|
||||||
<SaveBtn
|
hidden={!this.props.deleteBtn}
|
||||||
status={farmEvent.specialStatus || this.state.specialStatusLocal}
|
farmEvent={this.props.farmEvent}
|
||||||
onClick={() => this.commitViewModel()} />
|
dispatch={this.props.dispatch} />
|
||||||
<this.FarmEventDeleteButton />
|
<TzWarning deviceTimezone={this.props.deviceTimezone} />
|
||||||
<TzWarning deviceTimezone={this.props.deviceTimezone} />
|
</div>;
|
||||||
</DesignerPanelContent>
|
|
||||||
</DesignerPanel>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StartTimeFormProps {
|
||||||
|
isRegimen: boolean;
|
||||||
|
fieldGet(name: FarmEventViewModelKey): string;
|
||||||
|
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||||
|
timeSettings: TimeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StartTimeForm = (props: StartTimeFormProps) => {
|
||||||
|
const forceMidnight = props.isRegimen;
|
||||||
|
return <div className="start-time-form">
|
||||||
|
<label>
|
||||||
|
{t("Starts")}
|
||||||
|
</label>
|
||||||
|
<Row>
|
||||||
|
<Col xs={6}>
|
||||||
|
<BlurableInput
|
||||||
|
type="date"
|
||||||
|
className="add-event-start-date"
|
||||||
|
name="start_date"
|
||||||
|
value={props.fieldGet("startDate")}
|
||||||
|
onCommit={e => props.fieldSet("startDate", e.currentTarget.value)} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={6}>
|
||||||
|
<EventTimePicker
|
||||||
|
className="add-event-start-time"
|
||||||
|
name="start_time"
|
||||||
|
timeSettings={props.timeSettings}
|
||||||
|
value={props.fieldGet("startTime")}
|
||||||
|
onCommit={e => props.fieldSet("startTime", e.currentTarget.value)}
|
||||||
|
disabled={forceMidnight}
|
||||||
|
hidden={forceMidnight} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RepeatFormProps {
|
||||||
|
isRegimen: boolean;
|
||||||
|
fieldGet(name: FarmEventViewModelKey): string;
|
||||||
|
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||||
|
timeSettings: TimeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RepeatForm = (props: RepeatFormProps) => {
|
||||||
|
const allowRepeat = !props.isRegimen && props.fieldGet("timeUnit") !== NEVER;
|
||||||
|
return <div className="farm-event-repeat-options">
|
||||||
|
{!props.isRegimen
|
||||||
|
? <label>
|
||||||
|
<input type="checkbox"
|
||||||
|
onChange={e => props.fieldSet("timeUnit",
|
||||||
|
(!e.currentTarget.checked || props.isRegimen) ? "never" : "daily")}
|
||||||
|
disabled={props.isRegimen}
|
||||||
|
checked={allowRepeat} />
|
||||||
|
{t("Repeats?")}
|
||||||
|
</label>
|
||||||
|
: <div />}
|
||||||
|
<FarmEventRepeatForm
|
||||||
|
timeSettings={props.timeSettings}
|
||||||
|
disabled={!allowRepeat}
|
||||||
|
hidden={!allowRepeat}
|
||||||
|
fieldSet={props.fieldSet}
|
||||||
|
timeUnit={props.fieldGet("timeUnit") as TimeUnit}
|
||||||
|
repeat={props.fieldGet("repeat")}
|
||||||
|
endDate={props.fieldGet("endDate")}
|
||||||
|
endTime={props.fieldGet("endTime")}
|
||||||
|
dateError={dateCheck(props.fieldGet)}
|
||||||
|
timeError={timeCheck(props.fieldGet, props.timeSettings)} />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dateCheck = (
|
||||||
|
fieldGet: (name: FarmEventViewModelKey) => string
|
||||||
|
): string | undefined => {
|
||||||
|
const startDate = fieldGet("startDate");
|
||||||
|
const endDate = fieldGet("endDate");
|
||||||
|
if (!moment(endDate).isSameOrAfter(moment(startDate))) {
|
||||||
|
return t("End date must not be before start date.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timeCheck = (
|
||||||
|
fieldGet: (name: FarmEventViewModelKey) => string,
|
||||||
|
timeSettings: TimeSettings
|
||||||
|
): string | undefined => {
|
||||||
|
const startDate = fieldGet("startDate");
|
||||||
|
const startTime = fieldGet("startTime");
|
||||||
|
const endDate = fieldGet("endDate");
|
||||||
|
const endTime = fieldGet("endTime");
|
||||||
|
const start = offsetTime(startDate, startTime, timeSettings);
|
||||||
|
const end = offsetTime(endDate, endTime, timeSettings);
|
||||||
|
if (moment(start).isSameOrAfter(moment(end))) {
|
||||||
|
return t("End time must be after start time.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface FarmEventDeleteButtonProps {
|
||||||
|
hidden: boolean;
|
||||||
|
farmEvent: TaggedFarmEvent;
|
||||||
|
dispatch: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
|
||||||
|
<button className="fb-button red" hidden={props.hidden}
|
||||||
|
onClick={() =>
|
||||||
|
props.dispatch(destroy(props.farmEvent.uuid))
|
||||||
|
.then(() => {
|
||||||
|
history.push("/app/designer/events");
|
||||||
|
success(t("Deleted event."), t("Deleted"));
|
||||||
|
})}>
|
||||||
|
{t("Delete")}
|
||||||
|
</button>;
|
||||||
|
|
||||||
|
export interface FarmEventFormProps {
|
||||||
|
isRegimen: boolean;
|
||||||
|
fieldGet(name: FarmEventViewModelKey): string;
|
||||||
|
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||||
|
timeSettings: TimeSettings;
|
||||||
|
executableOptions: DropDownItem[];
|
||||||
|
executableSet(ddi: DropDownItem): void;
|
||||||
|
executableGet(): DropDownItem | undefined;
|
||||||
|
dispatch: Function;
|
||||||
|
specialStatus: SpecialStatus;
|
||||||
|
onSave(): void;
|
||||||
|
children?: React.ReactChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FarmEventForm = (props: FarmEventFormProps) => {
|
||||||
|
const { isRegimen, fieldGet, fieldSet, timeSettings } = props;
|
||||||
|
return <div className="farm-event-form">
|
||||||
|
<label>
|
||||||
|
{t("Sequence or Regimen")}
|
||||||
|
</label>
|
||||||
|
{props.executableOptions.length < 1 &&
|
||||||
|
<Help
|
||||||
|
text={Content.MISSING_EXECUTABLE}
|
||||||
|
customIcon={"exclamation-triangle"} />}
|
||||||
|
<FBSelect
|
||||||
|
list={props.executableOptions}
|
||||||
|
onChange={props.executableSet}
|
||||||
|
selectedItem={props.executableGet()} />
|
||||||
|
{props.children}
|
||||||
|
<StartTimeForm
|
||||||
|
isRegimen={isRegimen}
|
||||||
|
fieldGet={fieldGet}
|
||||||
|
fieldSet={fieldSet}
|
||||||
|
timeSettings={timeSettings} />
|
||||||
|
<RepeatForm
|
||||||
|
isRegimen={isRegimen}
|
||||||
|
fieldGet={fieldGet}
|
||||||
|
fieldSet={fieldSet}
|
||||||
|
timeSettings={props.timeSettings} />
|
||||||
|
<SaveBtn
|
||||||
|
status={props.specialStatus}
|
||||||
|
onClick={props.onSave} />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
|
@ -10,15 +10,12 @@ import { TimeUnit } from "farmbot/dist/resources/api_resources";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../interfaces";
|
import { TimeSettings } from "../../interfaces";
|
||||||
|
|
||||||
type Ev = React.SyntheticEvent<HTMLInputElement>;
|
export interface FarmEventRepeatFormProps {
|
||||||
type Key = keyof FarmEventViewModel;
|
|
||||||
|
|
||||||
export interface RepeatFormProps {
|
|
||||||
/** Should the form controls be grayed out? */
|
/** Should the form controls be grayed out? */
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
/** Should the form be shown _at all_? */
|
/** Should the form be shown _at all_? */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
onChange(key: Key, value: string): void;
|
fieldSet(name: keyof FarmEventViewModel, value: string): void;
|
||||||
timeUnit: TimeUnit;
|
timeUnit: TimeUnit;
|
||||||
repeat: string;
|
repeat: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
|
@ -31,11 +28,9 @@ export interface RepeatFormProps {
|
||||||
const indexKey: keyof DropDownItem = "value";
|
const indexKey: keyof DropDownItem = "value";
|
||||||
const OPTN_LOOKUP = keyBy(repeatOptions, indexKey);
|
const OPTN_LOOKUP = keyBy(repeatOptions, indexKey);
|
||||||
|
|
||||||
export function FarmEventRepeatForm(props: RepeatFormProps) {
|
export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) {
|
||||||
const { disabled, onChange, repeat, endDate, endTime, timeUnit } = props;
|
const { disabled, fieldSet, repeat, endDate, endTime, timeUnit } = props;
|
||||||
const changeHandler =
|
return props.hidden ? <div /> : <div className="farm-event-repeat-form">
|
||||||
(key: Key) => (e: Ev) => onChange(key, e.currentTarget.value);
|
|
||||||
return props.hidden ? <div /> : <div>
|
|
||||||
<label>
|
<label>
|
||||||
{t("Every")}
|
{t("Every")}
|
||||||
</label>
|
</label>
|
||||||
|
@ -48,13 +43,13 @@ export function FarmEventRepeatForm(props: RepeatFormProps) {
|
||||||
className="add-event-repeat-frequency"
|
className="add-event-repeat-frequency"
|
||||||
name="repeat"
|
name="repeat"
|
||||||
value={repeat}
|
value={repeat}
|
||||||
onCommit={changeHandler("repeat")}
|
onCommit={e => fieldSet("repeat", e.currentTarget.value)}
|
||||||
min={1} />
|
min={1} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={8}>
|
<Col xs={8}>
|
||||||
<FBSelect
|
<FBSelect
|
||||||
list={repeatOptions}
|
list={repeatOptions}
|
||||||
onChange={(e) => onChange("timeUnit", "" + e.value)}
|
onChange={ddi => fieldSet("timeUnit", "" + ddi.value)}
|
||||||
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
|
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -69,7 +64,7 @@ export function FarmEventRepeatForm(props: RepeatFormProps) {
|
||||||
className="add-event-end-date"
|
className="add-event-end-date"
|
||||||
name="endDate"
|
name="endDate"
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onCommit={changeHandler("endDate")}
|
onCommit={e => fieldSet("endDate", e.currentTarget.value)}
|
||||||
error={props.dateError} />
|
error={props.dateError} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
|
@ -79,7 +74,7 @@ export function FarmEventRepeatForm(props: RepeatFormProps) {
|
||||||
name="endTime"
|
name="endTime"
|
||||||
timeSettings={props.timeSettings}
|
timeSettings={props.timeSettings}
|
||||||
value={endTime}
|
value={endTime}
|
||||||
onCommit={changeHandler("endTime")}
|
onCommit={e => fieldSet("endTime", e.currentTarget.value)}
|
||||||
error={props.timeError} />
|
error={props.timeError} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Row } from "../../ui/index";
|
|
||||||
import { mapStateToProps } from "./map_state_to_props";
|
import { mapStateToProps } from "./map_state_to_props";
|
||||||
import {
|
import {
|
||||||
FarmEventProps, CalendarOccurrence, FarmEventState
|
FarmEventProps, CalendarOccurrence, FarmEventState
|
||||||
|
@ -100,28 +99,9 @@ export class PureFarmEvents
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** FarmEvents will generate some very unexpected results if the user has
|
render() {
|
||||||
* not set a timezone for the bot (defaults to 0 UTC offset, which could be
|
return <DesignerPanel panelName={"farm-event"} panel={Panel.FarmEvents}>
|
||||||
* far from user's local time). */
|
<DesignerNavTabs />
|
||||||
tzwarning = () => {
|
|
||||||
return <DesignerPanelContent panelName={"farm-event"}>
|
|
||||||
<Row>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<div className="farm-events">
|
|
||||||
<h2>Timezone Required</h2>
|
|
||||||
<p>
|
|
||||||
{t(Content.SET_TIMEZONE_HEADER)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Link to="/app/device">{t(Content.SET_TIMEZONE_BODY)}</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</DesignerPanelContent>;
|
|
||||||
};
|
|
||||||
|
|
||||||
normalContent = () => {
|
|
||||||
return <div className="farm-event-panel-normal-content">
|
|
||||||
<DesignerPanelTop
|
<DesignerPanelTop
|
||||||
panel={Panel.FarmEvents}
|
panel={Panel.FarmEvents}
|
||||||
linkTo={"/app/designer/events/add"}
|
linkTo={"/app/designer/events/add"}
|
||||||
|
@ -134,7 +114,6 @@ export class PureFarmEvents
|
||||||
placeholder={t("Search events...")} />
|
placeholder={t("Search events...")} />
|
||||||
</DesignerPanelTop>
|
</DesignerPanelTop>
|
||||||
<DesignerPanelContent panelName={"farm-event"}>
|
<DesignerPanelContent panelName={"farm-event"}>
|
||||||
|
|
||||||
<div className="farm-events">
|
<div className="farm-events">
|
||||||
<EmptyStateWrapper
|
<EmptyStateWrapper
|
||||||
notEmpty={this.props.calendarRows.length > 0}
|
notEmpty={this.props.calendarRows.length > 0}
|
||||||
|
@ -146,19 +125,8 @@ export class PureFarmEvents
|
||||||
</EmptyStateWrapper>
|
</EmptyStateWrapper>
|
||||||
</div>
|
</div>
|
||||||
</DesignerPanelContent>
|
</DesignerPanelContent>
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <DesignerPanel panelName={"farm-event"} panel={Panel.FarmEvents}>
|
|
||||||
<DesignerNavTabs />
|
|
||||||
{this.props.timezoneIsSet ? this.normalContent() : this.tzwarning()}
|
|
||||||
</DesignerPanel>;
|
</DesignerPanel>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is intentional. It is not a hack or a work around.
|
|
||||||
* It avoids mocking `connect` in unit tests.
|
|
||||||
* See testing pattern noted here: https://github.com/airbnb/enzyme/issues/98
|
|
||||||
*/
|
|
||||||
export const FarmEvents = connect(mapStateToProps)(PureFarmEvents);
|
export const FarmEvents = connect(mapStateToProps)(PureFarmEvents);
|
||||||
|
|
|
@ -6,6 +6,7 @@ interface HelpProps {
|
||||||
text: string;
|
text: string;
|
||||||
requireClick?: boolean;
|
requireClick?: boolean;
|
||||||
position?: PopoverPosition;
|
position?: PopoverPosition;
|
||||||
|
customIcon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Help(props: HelpProps) {
|
export function Help(props: HelpProps) {
|
||||||
|
@ -14,7 +15,7 @@ export function Help(props: HelpProps) {
|
||||||
interactionKind={props.requireClick
|
interactionKind={props.requireClick
|
||||||
? PopoverInteractionKind.CLICK : PopoverInteractionKind.HOVER}
|
? PopoverInteractionKind.CLICK : PopoverInteractionKind.HOVER}
|
||||||
popoverClassName={"help"}>
|
popoverClassName={"help"}>
|
||||||
<i className="fa fa-question-circle help-icon"></i>
|
<i className={`fa fa-${props.customIcon || "question-circle"} help-icon`} />
|
||||||
<div>{t(props.text)}</div>
|
<div>{t(props.text)}</div>
|
||||||
</Popover>;
|
</Popover>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue