Farmbot-Web-App/frontend/farm_designer/farm_events/edit_fe_form.tsx

549 lines
19 KiB
TypeScript
Raw Normal View History

2017-06-29 12:54:02 -06:00
import * as React from "react";
2019-02-04 13:54:17 -07:00
import moment from "moment";
2019-07-17 13:38:26 -06:00
import { success, error } from "../../toast/toast";
2018-04-20 00:37:20 -06:00
import {
2018-12-20 20:18:10 -07:00
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen,
2020-01-03 13:17:56 -07:00
ParameterApplication,
2018-08-01 07:09:30 -06:00
} from "farmbot";
2018-08-02 15:46:58 -06:00
import { ExecutableQuery } from "../interfaces";
import { formatTime, formatDate } from "./map_state_to_props_add_edit";
2017-06-29 12:54:02 -06:00
import {
BlurableInput,
Col, Row,
SaveBtn,
FBSelect,
2020-01-03 13:17:56 -07:00
DropDownItem,
Help,
2018-12-30 11:57:22 -07:00
} from "../../ui";
2018-12-20 20:18:10 -07:00
import { destroy, save, overwrite } from "../../api/crud";
2017-06-29 12:54:02 -06:00
import { history } from "../../history";
2019-01-13 16:39:26 -07:00
import { betterMerge, parseIntInput } from "../../util";
2017-06-29 12:54:02 -06:00
import { maybeWarnAboutMissedTasks } from "./util";
2017-07-26 09:37:19 -06:00
import { FarmEventRepeatForm } from "./farm_event_repeat_form";
import { scheduleForFarmEvent } from "./calendar/scheduler";
import { executableType } from "../util";
import { Content } from "../../constants";
import { EventTimePicker } from "./event_time_picker";
2018-01-02 14:32:10 -07:00
import { TzWarning } from "./tz_warning";
2018-04-20 00:37:20 -06:00
import { nextRegItemTimes } from "./map_state_to_props";
import { first } from "lodash";
import {
TimeUnit, ExecutableType, FarmEvent
} from "farmbot/dist/resources/api_resources";
2018-12-20 20:18:10 -07:00
import { LocalsList } from "../../sequences/locals_list/locals_list";
import { ResourceIndex } from "../../resources/interfaces";
2019-05-15 10:13:17 -06:00
import { ShouldDisplay } from "../../devices/interfaces";
2018-12-20 20:18:10 -07:00
import {
2019-02-22 19:09:40 -07:00
addOrEditParamApps, variableList, getRegimenVariableData
} from "../../sequences/locals_list/variable_support";
2018-12-30 11:57:22 -07:00
import {
2020-01-03 13:17:56 -07:00
AllowedVariableNodes,
2018-12-30 11:57:22 -07:00
} from "../../sequences/locals_list/locals_list_support";
2019-04-02 13:59:37 -06:00
import { t } from "../../i18next_wrapper";
2019-04-09 23:17:03 -06:00
import { TimeSettings } from "../../interfaces";
2020-02-26 11:10:59 -07:00
import { ErrorBoundary } from "../../error_boundary";
2017-06-29 12:54:02 -06:00
export const NEVER: TimeUnit = "never";
2017-07-20 12:26:44 -06:00
/** Separate each of the form fields into their own interface. Recombined later
2017-06-29 12:54:02 -06:00
* on save.
*/
2017-07-26 09:37:19 -06:00
export interface FarmEventViewModel {
2018-12-20 20:18:10 -07:00
id: number | undefined;
2017-07-26 09:37:19 -06:00
startDate: string;
startTime: string;
endDate: string;
endTime: string;
2017-06-29 12:54:02 -06:00
repeat: string;
2017-07-26 09:37:19 -06:00
timeUnit: string;
2017-06-29 12:54:02 -06:00
executable_type: string;
executable_id: string;
2019-04-09 23:17:03 -06:00
timeSettings: TimeSettings;
2019-02-22 19:09:40 -07:00
body?: ParameterApplication[];
2017-06-29 12:54:02 -06:00
}
2018-12-30 11:57:22 -07:00
2020-01-03 13:17:56 -07:00
export type FarmEventViewModelKey = keyof FarmEventViewModel;
2017-06-29 12:54:02 -06:00
/** Breaks up a TaggedFarmEvent into a structure that can easily be used
* by the edit form.
* USE CASE EXAMPLE: We have a "date" and "time" field that are created from
* a single "start_time" FarmEvent field. */
export function destructureFarmEvent(
2019-04-09 23:17:03 -06:00
fe: TaggedFarmEvent, timeSettings: TimeSettings): FarmEventViewModel {
2017-06-29 12:54:02 -06:00
return {
2018-12-20 20:18:10 -07:00
id: fe.body.id,
2019-04-09 23:17:03 -06:00
startDate: formatDate((fe.body.start_time).toString(), timeSettings),
startTime: formatTime((fe.body.start_time).toString(), timeSettings),
endDate: formatDate((fe.body.end_time || new Date()).toString(), timeSettings),
endTime: formatTime((fe.body.end_time || new Date()).toString(), timeSettings),
2017-06-29 12:54:02 -06:00
repeat: (fe.body.repeat || 1).toString(),
2017-07-26 09:37:19 -06:00
timeUnit: fe.body.time_unit,
2017-06-29 12:54:02 -06:00
executable_type: fe.body.executable_type,
2017-12-29 09:47:54 -07:00
executable_id: (fe.body.executable_id || "").toString(),
2019-04-09 23:17:03 -06:00
timeSettings,
2018-12-20 20:18:10 -07:00
body: fe.body.body,
2017-07-25 16:03:18 -06:00
};
2017-06-29 12:54:02 -06:00
}
2018-12-30 11:57:22 -07:00
2018-12-27 10:23:04 -07:00
const startTimeWarning = () => {
const message =
2019-04-09 19:52:12 -06:00
t("Event start time needs to be in the future, not the past.");
const title = t("Unable to save event.");
2018-12-27 10:23:04 -07:00
error(message, title);
};
2018-12-30 11:57:22 -07:00
2019-07-17 13:38:26 -06:00
const nothingToRunWarning = () => {
const message =
t("All items scheduled before the start time. Nothing to run.");
const title = t("Unable to save event.");
error(message, title);
};
2018-12-27 10:23:04 -07:00
type RecombineOptions = { forceRegimensToMidnight: boolean };
2017-12-29 09:47:54 -07:00
2018-12-20 20:18:10 -07:00
/** Take a FormViewModel and recombine the fields into a FarmEvent
2017-06-29 12:54:02 -06:00
* that can be used to apply updates (such as a PUT request to the API). */
export function recombine(vm: FarmEventViewModel,
2018-12-27 10:23:04 -07:00
options: RecombineOptions): TaggedFarmEvent["body"] {
// Make sure that `repeat` is set to `never` when dealing with regimens.
2017-08-28 05:49:13 -06:00
const isReg = vm.executable_type === "Regimen";
const startTime = isReg && options.forceRegimensToMidnight
? "00:00" : vm.startTime;
2017-06-29 12:54:02 -06:00
return {
2018-12-20 20:18:10 -07:00
id: vm.id,
2019-04-09 23:17:03 -06:00
start_time: offsetTime(vm.startDate, startTime, vm.timeSettings),
end_time: offsetTime(vm.endDate, vm.endTime, vm.timeSettings),
repeat: parseInt(vm.repeat, 10) || 1,
time_unit: (isReg ? "never" : vm.timeUnit) as TimeUnit,
2019-01-13 16:39:26 -07:00
executable_id: parseIntInput(vm.executable_id),
2017-06-29 12:54:02 -06:00
executable_type: vm.executable_type as ("Sequence" | "Regimen"),
2018-12-20 20:18:10 -07:00
body: vm.body,
2017-06-29 12:54:02 -06:00
};
}
2019-04-09 23:17:03 -06:00
export function offsetTime(
date: string, time: string, timeSettings: TimeSettings): string {
const out = moment(date).utcOffset(timeSettings.utcOffset);
2018-01-03 20:56:29 -07:00
const [hrs, min] = time.split(":").map(x => parseInt(x));
out.hours(hrs);
out.minutes(min);
return out.toISOString();
2017-12-29 09:47:54 -07:00
}
2017-07-26 14:18:28 -06:00
export interface EditFEProps {
2017-06-29 12:54:02 -06:00
deviceTimezone: string | undefined;
executableOptions: DropDownItem[];
2017-06-29 12:54:02 -06:00
repeatOptions: DropDownItem[];
farmEvent: TaggedFarmEvent;
dispatch: Function;
findExecutable: ExecutableQuery;
title: string;
deleteBtn?: boolean;
2019-04-09 23:17:03 -06:00
timeSettings: TimeSettings;
2018-04-05 14:05:54 -06:00
autoSyncEnabled: boolean;
2018-12-20 20:18:10 -07:00
resources: ResourceIndex;
shouldDisplay: ShouldDisplay;
2017-06-29 12:54:02 -06:00
}
2020-01-03 13:17:56 -07:00
export interface EditFEFormState {
2018-12-30 11:57:22 -07:00
/**
* Hold a partial FarmEvent locally containing only updates made by the form.
*/
2017-06-29 12:54:02 -06:00
fe: Partial<FarmEventViewModel>;
/**
* This form has local state and does not cause any global state changes when
* editing.
*
* Example: Navigating away from the page while editing will discard changes.
*/
specialStatusLocal: SpecialStatus;
2017-07-25 16:03:18 -06:00
}
2017-06-29 12:54:02 -06:00
2020-01-03 13:17:56 -07:00
export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
state: EditFEFormState = { fe: {}, specialStatusLocal: SpecialStatus.SAVED };
2017-06-29 12:54:02 -06:00
2018-12-30 11:57:22 -07:00
/** API data for the FarmEvent to which form updates can be applied. */
2017-12-29 10:39:04 -07:00
get viewModel() {
2019-04-09 23:17:03 -06:00
return destructureFarmEvent(this.props.farmEvent, this.props.timeSettings);
2017-12-29 10:39:04 -07:00
}
2017-06-29 12:54:02 -06:00
get executable() {
2017-08-28 05:49:13 -06:00
const et = this.fieldGet("executable_type");
const id = parseInt(this.fieldGet("executable_id"));
2017-07-25 16:03:18 -06:00
if (et === "Sequence" || et === "Regimen") {
return this.props.findExecutable(et, id);
2017-06-29 12:54:02 -06:00
} else {
2017-07-25 16:03:18 -06:00
throw new Error(`${et} is not a valid executable_type`);
2017-06-29 12:54:02 -06:00
}
}
2018-12-30 11:57:22 -07:00
get isReg() {
return this.fieldGet("executable_type") === "Regimen";
}
2019-02-22 19:09:40 -07:00
/** Executable requires variables or a user has manually added bodyVariables. */
2018-12-30 11:57:22 -07:00
get needsVariables() {
const varData = this.props.resources.sequenceMetas[this.executable.uuid];
return Object.keys(varData || {}).length > 0;
}
2018-12-20 20:18:10 -07:00
get variableData() {
2019-01-11 10:56:11 -07:00
if (this.executable.kind === "Regimen") {
const regimenVariables = this.executable.body.body;
return getRegimenVariableData(regimenVariables, this.props.resources);
}
2018-12-20 20:18:10 -07:00
return this.props.resources.sequenceMetas[this.executable.uuid];
}
2019-03-20 14:14:47 -06:00
get bodyVariables(): ParameterApplication[] {
return this.state.fe.body || this.props.farmEvent.body.body || [];
2018-12-20 20:18:10 -07:00
}
2019-02-22 19:09:40 -07:00
overwriteStateFEBody = (newBody: ParameterApplication[]) => {
2018-12-30 11:57:22 -07:00
const state = this.state;
state.fe.body = newBody;
this.setState(state);
}
2019-02-22 19:09:40 -07:00
editBodyVariables = (bodyVariables: ParameterApplication[]) =>
(variable: ParameterApplication) => {
const body = addOrEditParamApps(bodyVariables, variable);
2018-12-30 11:57:22 -07:00
this.overwriteStateFEBody(body);
this.setState({ specialStatusLocal: SpecialStatus.DIRTY });
2018-12-26 11:00:41 -07:00
}
2018-12-20 20:18:10 -07:00
LocalsList = () => <LocalsList
2019-02-22 19:09:40 -07:00
bodyVariables={this.bodyVariables}
2018-12-20 20:18:10 -07:00
variableData={this.variableData}
sequenceUuid={this.executable.uuid}
resources={this.props.resources}
2019-03-20 14:14:47 -06:00
onChange={this.editBodyVariables(this.bodyVariables)}
2019-02-22 19:09:40 -07:00
allowedVariableNodes={AllowedVariableNodes.variable}
2018-12-20 20:18:10 -07:00
shouldDisplay={this.props.shouldDisplay} />
2019-02-10 22:10:29 -07:00
executableSet = (ddi: DropDownItem) => {
if (ddi.value) {
2019-06-28 13:05:37 -06:00
const { id, executable_type } = this.props.farmEvent.body;
const prev_executable_type = executable_type;
2019-02-10 22:10:29 -07:00
const next_executable_type = executableType(ddi.headingId);
2019-06-28 13:05:37 -06:00
if (id && prev_executable_type !== next_executable_type) {
2019-06-21 15:43:46 -06:00
error(t("Cannot change between Sequences and Regimens."));
2019-04-09 19:52:12 -06:00
history.push("/app/designer/events");
2018-04-20 00:37:20 -06:00
} else {
2019-02-10 22:10:29 -07:00
const { uuid } = this.props.findExecutable(
next_executable_type, parseInt("" + ddi.value));
2018-12-20 20:18:10 -07:00
const varData = this.props.resources.sequenceMetas[uuid];
2020-01-03 13:17:56 -07:00
const update: EditFEFormState = {
2018-04-20 00:37:20 -06:00
fe: {
2019-02-10 22:10:29 -07:00
executable_type: next_executable_type,
executable_id: (ddi.value || "").toString(),
2018-04-20 00:37:20 -06:00
},
specialStatusLocal: SpecialStatus.DIRTY
};
2019-02-22 19:09:40 -07:00
this.overwriteStateFEBody(variableList(varData) || []);
2018-04-20 00:37:20 -06:00
this.setState(betterMerge(this.state, update));
}
2017-06-29 12:54:02 -06:00
}
}
executableGet = (): DropDownItem => {
2017-08-28 05:49:13 -06:00
const headingId: ExecutableType =
2018-01-02 10:07:54 -07:00
(this.executable.kind === "Sequence") ? "Sequence" : "Regimen";
2017-06-29 12:54:02 -06:00
return {
value: this.executable.body.id || 0,
label: this.executable.body.name,
headingId
2017-07-25 16:03:18 -06:00
};
2017-06-29 12:54:02 -06:00
}
2020-02-28 09:34:28 -07:00
fieldSet = (key: FarmEventViewModelKey, value: string) =>
2020-01-03 13:17:56 -07:00
// A merge is required to not overwrite `fe`.
this.setState(betterMerge(this.state, {
2020-02-28 09:34:28 -07:00
fe: { [key]: value },
2017-08-16 08:32:48 -06:00
specialStatusLocal: SpecialStatus.DIRTY
2020-01-03 13:17:56 -07:00
}))
2020-02-28 09:34:28 -07:00
fieldGet = (key: FarmEventViewModelKey): string =>
(this.state.fe[key] || this.viewModel[key] || "").toString()
2018-04-20 00:37:20 -06:00
nextItemTime = (fe: FarmEvent, now: moment.Moment
): moment.Moment | undefined => {
2019-04-09 23:17:03 -06:00
const { timeSettings } = this.props;
2018-04-20 00:37:20 -06:00
const kind = fe.executable_type;
const start = fe.start_time;
const isRegimen = (x: TaggedSequence | TaggedRegimen): x is TaggedRegimen =>
!!(x.kind === "Regimen");
switch (kind) {
case "Sequence":
return first(scheduleForFarmEvent(fe, now).items);
case "Regimen":
const r = this.props.findExecutable(kind, fe.executable_id);
const nextItem = isRegimen(r)
2019-04-09 23:17:03 -06:00
? first(nextRegItemTimes(r.body.regimen_items, start, now, timeSettings))
2018-04-20 00:37:20 -06:00
: undefined;
const futureStartTimeFallback = moment(start) > now
? moment(start)
: undefined;
return nextItem || futureStartTimeFallback;
default:
throw new Error(`${kind} is not a valid executable_type`);
}
}
/** Rejects save of Farm Event if: */
2018-12-20 20:18:10 -07:00
maybeRejectStartTime = (f: FarmEvent, now = moment()) => {
2018-04-20 00:37:20 -06:00
/** adding a new event (editing repeats for ongoing events is allowed) */
const newEvent = this.props.title.toLowerCase().includes("add");
/** start time is in the past */
const inThePast = moment(f.start_time) < now;
2019-05-15 10:13:17 -06:00
/** is a sequence event */
2018-04-20 00:37:20 -06:00
const sequenceEvent = !this.isReg;
2019-05-15 10:13:17 -06:00
return newEvent && (inThePast && sequenceEvent);
2018-04-20 00:37:20 -06:00
}
2018-12-30 11:57:22 -07:00
/** Merge and recombine FarmEvent form updates into and updated FarmEvent. */
get updatedFarmEvent() {
/** ViewModel with INVALID `body` (must be replaced). */
const vm = betterMerge(this.viewModel, this.state.fe);
const oldBodyData = this.needsVariables ? this.viewModel.body : [];
vm.body = this.state.fe.body || oldBodyData;
2019-05-15 10:13:17 -06:00
const opts: RecombineOptions = { forceRegimensToMidnight: true };
2018-12-30 11:57:22 -07:00
return recombine(vm, opts);
}
2019-07-17 13:38:26 -06:00
/** Use the next item run time to display toast messages. */
nextRunTimeActions = (now = moment()) => {
2018-12-30 11:57:22 -07:00
const nextRun = this.nextItemTime(this.props.farmEvent.body, now);
if (nextRun) {
const nextRunText = this.props.autoSyncEnabled
2019-04-09 19:52:12 -06:00
? t(`The next item in this event will run {{timeFromNow}}.`,
2018-12-30 11:57:22 -07:00
{ timeFromNow: nextRun.from(now) })
2019-04-09 19:52:12 -06:00
: t(`The next item in this event will run {{timeFromNow}}, but
2018-12-30 11:57:22 -07:00
you must first SYNC YOUR DEVICE. If you do not sync, the event will
not run.`.replace(/\s+/g, " "), { timeFromNow: nextRun.from(now) });
success(nextRunText);
}
}
2018-04-20 00:37:20 -06:00
/** Once saved, if
* - Regimen Farm Event:
* * If scheduled for today, warn about the possibility of missing tasks.
* * Display the start time difference from now and maybe prompt to sync.
2019-07-17 13:38:26 -06:00
* * Return to calendar view.
2018-04-20 00:37:20 -06:00
* - Sequence Farm Event:
* * Determine the time for the next item to be run.
* * If auto-sync is disabled, prompt the user to sync.
2019-07-17 13:38:26 -06:00
* * Return to calendar view.
2018-04-20 00:37:20 -06:00
*/
commitViewModel = (now = moment()) => {
2018-12-30 11:57:22 -07:00
if (this.maybeRejectStartTime(this.updatedFarmEvent)) {
2018-12-27 10:23:04 -07:00
return startTimeWarning();
}
2019-07-17 13:38:26 -06:00
if (!this.nextItemTime(this.updatedFarmEvent, now)) {
return nothingToRunWarning();
}
2020-01-03 13:17:56 -07:00
const { dispatch } = this.props;
dispatch(overwrite(this.props.farmEvent, this.updatedFarmEvent));
dispatch(save(this.props.farmEvent.uuid))
2017-06-29 12:54:02 -06:00
.then(() => {
this.setState({ specialStatusLocal: SpecialStatus.SAVED });
2020-01-03 13:17:56 -07:00
dispatch(maybeWarnAboutMissedTasks(this.props.farmEvent,
2018-12-30 11:57:22 -07:00
() => alert(t(Content.REGIMEN_TODAY_SKIPPED_ITEM_RISK)), now));
2019-07-17 13:38:26 -06:00
this.nextRunTimeActions(now);
history.push("/app/designer/events");
2017-06-29 12:54:02 -06:00
})
.catch(() => {
2019-04-09 19:52:12 -06:00
error(t("Unable to save event."));
2017-08-16 08:32:48 -06:00
this.setState({ specialStatusLocal: SpecialStatus.DIRTY });
2017-06-29 12:54:02 -06:00
});
}
2018-12-30 11:57:22 -07:00
2020-01-03 13:17:56 -07:00
render() {
const { farmEvent } = this.props;
return <div className="edit-farm-event-form">
2020-02-26 11:10:59 -07:00
<ErrorBoundary>
<FarmEventForm
isRegimen={this.isReg}
fieldGet={this.fieldGet}
fieldSet={this.fieldSet}
timeSettings={this.props.timeSettings}
executableOptions={this.props.executableOptions}
executableSet={this.executableSet}
executableGet={this.executableGet}
dispatch={this.props.dispatch}
specialStatus={farmEvent.specialStatus
|| this.state.specialStatusLocal}
onSave={() => this.commitViewModel()}>
<ErrorBoundary>
<this.LocalsList />
</ErrorBoundary>
</FarmEventForm>
</ErrorBoundary>
2020-01-03 13:17:56 -07:00
<FarmEventDeleteButton
hidden={!this.props.deleteBtn}
farmEvent={this.props.farmEvent}
dispatch={this.props.dispatch} />
<TzWarning deviceTimezone={this.props.deviceTimezone} />
2018-12-30 11:57:22 -07:00
</div>;
}
2020-01-03 13:17:56 -07:00
}
2018-12-30 11:57:22 -07:00
2020-01-03 13:17:56 -07:00
export interface StartTimeFormProps {
isRegimen: boolean;
2020-02-28 09:34:28 -07:00
fieldGet(key: FarmEventViewModelKey): string;
fieldSet(key: FarmEventViewModelKey, value: string): void;
2020-01-03 13:17:56 -07:00
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;
2020-02-28 09:34:28 -07:00
fieldGet(key: FarmEventViewModelKey): string;
fieldSet(key: FarmEventViewModelKey, value: string): void;
2020-01-03 13:17:56 -07:00
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>
2018-12-30 11:57:22 -07:00
<input type="checkbox"
2020-02-28 09:34:28 -07:00
name="timeUnit"
2020-01-03 13:17:56 -07:00
onChange={e => props.fieldSet("timeUnit",
(!e.currentTarget.checked || props.isRegimen) ? "never" : "daily")}
disabled={props.isRegimen}
2018-12-30 11:57:22 -07:00
checked={allowRepeat} />
2020-01-03 13:17:56 -07:00
{t("Repeats?")}
</label>
2020-02-28 09:34:28 -07:00
: <div className={"no-repeat"} />}
2020-01-03 13:17:56 -07:00
<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>;
};
2019-01-13 16:39:26 -07:00
2020-01-03 13:17:56 -07:00
export const dateCheck = (
2020-02-28 09:34:28 -07:00
fieldGet: (key: FarmEventViewModelKey) => string
2020-01-03 13:17:56 -07:00
): 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.");
2019-01-13 16:39:26 -07:00
}
2020-01-03 13:17:56 -07:00
};
2019-01-13 16:39:26 -07:00
2020-01-03 13:17:56 -07:00
export const timeCheck = (
2020-02-28 09:34:28 -07:00
fieldGet: (key: FarmEventViewModelKey) => string,
2020-01-03 13:17:56 -07:00
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.");
}
2020-01-03 13:17:56 -07:00
};
2017-06-29 12:54:02 -06:00
2020-01-03 13:17:56 -07:00
export interface FarmEventDeleteButtonProps {
hidden: boolean;
farmEvent: TaggedFarmEvent;
dispatch: Function;
}
2018-12-30 11:57:22 -07:00
2020-01-03 13:17:56 -07:00
export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
<button className="fb-button red" hidden={props.hidden}
2020-02-28 09:34:28 -07:00
title={t("Delete")}
2020-01-03 13:17:56 -07:00
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;
2020-02-28 09:34:28 -07:00
fieldGet(key: FarmEventViewModelKey): string;
fieldSet(key: FarmEventViewModelKey, value: string): void;
2020-01-03 13:17:56 -07:00
timeSettings: TimeSettings;
executableOptions: DropDownItem[];
executableSet(ddi: DropDownItem): void;
executableGet(): DropDownItem | undefined;
dispatch: Function;
specialStatus: SpecialStatus;
onSave(): void;
children?: React.ReactChild;
2017-06-29 12:54:02 -06:00
}
2020-01-03 13:17:56 -07:00
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>;
};