From f6d6ef7c0c3895386c128b04def1309ea67d0c62 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Tue, 23 Jul 2019 23:03:44 -0600 Subject: [PATCH] Show next run time for programs --- client/components/ProgramSequenceView.tsx | 6 ++-- client/components/ProgramTable.tsx | 7 ++++ client/pages/ProgramPage.tsx | 41 ++++++++++++++++++++--- client/styles/DeviceView.scss | 5 +++ common/sprinklersRpc/Program.ts | 7 ++-- common/sprinklersRpc/mqtt/MqttProgram.ts | 2 ++ common/sprinklersRpc/schema/index.ts | 5 +-- 7 files changed, 61 insertions(+), 12 deletions(-) diff --git a/client/components/ProgramSequenceView.tsx b/client/components/ProgramSequenceView.tsx index 28bd9d0..aed3727 100644 --- a/client/components/ProgramSequenceView.tsx +++ b/client/components/ProgramSequenceView.tsx @@ -105,16 +105,17 @@ class ProgramSequenceItem extends React.Component<{ const ProgramSequenceItemD = SortableElement(ProgramSequenceItem); +// tslint:disable: no-shadowed-variable const ProgramSequenceList = SortableContainer( observer( - (props: { + function ProgramSequenceList(props: { className: string; list: ProgramItem[]; sections: Section[]; editing: boolean; onChange: ItemChangeHandler; onRemove: ItemRemoveHandler; - }) => { + }) { const { className, list, sections, ...rest } = props; const listItems = list.map((item, index) => { const key = `item-${index}`; @@ -132,7 +133,6 @@ const ProgramSequenceList = SortableContainer( return ; } ), - { withRef: true } ); @observer diff --git a/client/components/ProgramTable.tsx b/client/components/ProgramTable.tsx index 4acac9e..a4869f9 100644 --- a/client/components/ProgramTable.tsx +++ b/client/components/ProgramTable.tsx @@ -8,6 +8,7 @@ import { ProgramSequenceView, ScheduleView } from "@client/components"; import * as route from "@client/routePaths"; import { ISprinklersDevice } from "@common/httpApi"; import { Program, SprinklersDevice } from "@common/sprinklersRpc"; +import moment = require("moment"); @observer class ProgramRows extends React.Component<{ @@ -69,6 +70,12 @@ class ProgramRows extends React.Component<{

Sequence:

{" "} Schedule: } /> +

Next run:

+ { + program.nextRun + ? + : + } diff --git a/client/pages/ProgramPage.tsx b/client/pages/ProgramPage.tsx index b93e59c..1caee46 100644 --- a/client/pages/ProgramPage.tsx +++ b/client/pages/ProgramPage.tsx @@ -20,8 +20,9 @@ import { AppState, injectState } from "@client/state"; import { ISprinklersDevice } from "@common/httpApi"; import log from "@common/logger"; import { Program, SprinklersDevice } from "@common/sprinklersRpc"; -import { action } from "mobx"; import classNames = require("classnames"); +import { action } from "mobx"; +import * as moment from "moment"; interface ProgramPageProps extends RouteComponentProps<{ deviceId: string; programId: string }> { @@ -186,7 +187,6 @@ class ProgramPage extends React.Component { toggle label="Enabled" checked={enabled} - readOnly={!editing} onChange={this.onEnabledChange} /> { editing={editing} label={

Schedule

} /> + { !editing && ( +

Next run:

) + } + { + !editing && ( + program.nextRun + ? + : + ) + } {this.renderActions(program)} @@ -243,6 +253,10 @@ class ProgramPage extends React.Component { }, err => { log.error({ err }, "error updating Program"); + this.props.appState.uiStore.addMessage({ + error: true, + content: `Error updating program: ${err}`, + }); } ); this.stopEditing(); @@ -268,11 +282,28 @@ class ProgramPage extends React.Component { @action.bound private onEnabledChange(e: any, p: CheckboxProps) { - if (this.programView) { - this.programView.enabled = p.checked!; + if (p.checked !== undefined && this.program) { + this.program.enabled = p.checked; + this.program.update().then( + data => { + log.info({ data }, "Program updated"); + this.props.appState.uiStore.addMessage({ + success: true, + content: `Program ${this.program!.name} ${this.program!.enabled ? "enabled" : "disabled"}`, + timeout: 2000, + }); + }, + err => { + log.error({ err }, "error updating Program"); + this.props.appState.uiStore.addMessage({ + error: true, + content: `Error updating program: ${err}`, + }); + } + ); } } } -const DecoratedProgramPage = injectState(withRouter(observer(ProgramPage))); +const DecoratedProgramPage = injectState(withRouter(ProgramPage)); export default DecoratedProgramPage; diff --git a/client/styles/DeviceView.scss b/client/styles/DeviceView.scss index 3698043..fb9f6bf 100644 --- a/client/styles/DeviceView.scss +++ b/client/styles/DeviceView.scss @@ -78,6 +78,11 @@ $connected-color: #13d213; color: green; } +.program--nextRun { + display: inline-block; + padding-right: 0.5em; +} + .ui.modal.programEditor { &.editing > .content { min-height: 80vh; diff --git a/common/sprinklersRpc/Program.ts b/common/sprinklersRpc/Program.ts index 7ab916d..0c176df 100644 --- a/common/sprinklersRpc/Program.ts +++ b/common/sprinklersRpc/Program.ts @@ -32,6 +32,8 @@ export class Program { sequence: ProgramItem[] = []; @observable running: boolean = false; + @observable + nextRun: Date | null = null; constructor(device: SprinklersDevice, id: number, data?: Partial) { this.device = device; @@ -60,7 +62,8 @@ export class Program { enabled: this.enabled, running: this.running, schedule: this.schedule.clone(), - sequence: this.sequence.slice() + sequence: this.sequence.slice(), + nextRun: this.nextRun, }); } @@ -68,7 +71,7 @@ export class Program { return ( `Program{name="${this.name}", enabled=${this.enabled}, schedule=${ this.schedule - }, ` + `sequence=${this.sequence}, running=${this.running}}` + }, ` + `sequence=${this.sequence}, running=${this.running}, nextRun=${this.nextRun}}` ); } } diff --git a/common/sprinklersRpc/mqtt/MqttProgram.ts b/common/sprinklersRpc/mqtt/MqttProgram.ts index 330f34e..59b1b0c 100644 --- a/common/sprinklersRpc/mqtt/MqttProgram.ts +++ b/common/sprinklersRpc/mqtt/MqttProgram.ts @@ -7,6 +7,8 @@ export class MqttProgram extends s.Program { onMessage(payload: string, topic: string | undefined) { if (topic === "running") { this.running = payload === "true"; + } else if (topic === "nextRun") { + this.nextRun = (payload.length > 0) ? new Date(Number(payload) * 1000.0) : null; } else if (topic == null) { this.updateFromJSON(JSON.parse(payload)); } diff --git a/common/sprinklersRpc/schema/index.ts b/common/sprinklersRpc/schema/index.ts index 86fd9c3..ac767b8 100644 --- a/common/sprinklersRpc/schema/index.ts +++ b/common/sprinklersRpc/schema/index.ts @@ -1,4 +1,4 @@ -import { createSimpleSchema, ModelSchema, object, primitive } from "serializr"; +import { createSimpleSchema, ModelSchema, object, primitive, date } from "serializr"; import * as s from ".."; import list from "./list"; @@ -85,7 +85,8 @@ export const program: ModelSchema = { enabled: primitive(), schedule: object(schedule), sequence: list(object(programItem)), - running: primitive() + running: primitive(), + nextRun: date(), } };