Alex Mikhalev
7 years ago
10 changed files with 314 additions and 112 deletions
@ -1,107 +0,0 @@
@@ -1,107 +0,0 @@
|
||||
import { observer } from "mobx-react"; |
||||
import * as moment from "moment"; |
||||
import * as React from "react"; |
||||
import { Checkbox, Form, Input, InputOnChangeData, CheckboxProps } from "semantic-ui-react"; |
||||
|
||||
import { DateOfYear, Schedule, TimeOfDay, Weekday, WEEKDAYS } from "@common/sprinklersRpc"; |
||||
|
||||
function timeToString(time: TimeOfDay) { |
||||
return moment(time).format("LTS"); |
||||
} |
||||
|
||||
function formatDateOfYear(day: DateOfYear | null, prefix: React.ReactNode, editing: boolean) { |
||||
if (day == null && !editing) { |
||||
return null; |
||||
} |
||||
const format = (day && day.year === 0) ? "M/D" : "l"; |
||||
const dayString = moment(day || "").format(format); |
||||
let dayNode: React.ReactNode; |
||||
if (editing) { |
||||
dayNode = <Input value={dayString} />; |
||||
} else { |
||||
dayNode = <span>{dayString}</span>; |
||||
} |
||||
return <Form.Field inline>{prefix}{dayNode}</Form.Field>; |
||||
} |
||||
|
||||
interface WeekdaysViewProps { |
||||
weekdays: Weekday[]; |
||||
editing: boolean; |
||||
onChange?: (newWeekdays: Weekday[]) => void; |
||||
} |
||||
|
||||
function WeekdaysView({weekdays, editing, onChange}: WeekdaysViewProps) { |
||||
let node: React.ReactNode; |
||||
if (editing) { |
||||
node = WEEKDAYS.map((weekday) => { |
||||
const checked = weekdays.find((wd) => wd === weekday) != null; |
||||
const name = Weekday[weekday]; |
||||
const toggleWeekday = (event: React.FormEvent<HTMLInputElement>, data: CheckboxProps) => { |
||||
if (!onChange) { |
||||
return; |
||||
} |
||||
if (data.checked && !checked) { |
||||
onChange(weekdays.concat([weekday])); |
||||
} else if (!data.checked && checked) { |
||||
onChange(weekdays.filter((wd) => wd !== weekday)); |
||||
} |
||||
} |
||||
return ( |
||||
<Form.Field control={Checkbox} label={name} checked={checked} key={weekday} onChange={toggleWeekday} /> |
||||
); |
||||
}); |
||||
} else { |
||||
node = weekdays.map((weekday) => |
||||
Weekday[weekday]).join(", "); |
||||
} |
||||
return ( |
||||
<Form.Group inline> |
||||
<label>On</label> {node} |
||||
</Form.Group> |
||||
) |
||||
} |
||||
|
||||
export interface ScheduleViewProps { |
||||
schedule: Schedule; |
||||
editing?: boolean; |
||||
} |
||||
|
||||
@observer |
||||
export default class ScheduleView extends React.Component<ScheduleViewProps> { |
||||
render() { |
||||
const { schedule } = this.props; |
||||
const editing = this.props.editing != null ? this.props.editing : false; |
||||
|
||||
let times: React.ReactNode; |
||||
if (editing) { |
||||
times = schedule.times |
||||
.map((time, i) => { |
||||
const onChange = (event: React.SyntheticEvent, d: InputOnChangeData) => { |
||||
const m = moment(d.value, ["LTS"]); |
||||
schedule.times[i] = TimeOfDay.fromMoment(m); |
||||
}; |
||||
return <Input value={timeToString(time)} key={i} onChange={onChange} />; |
||||
}); |
||||
} else { |
||||
times = schedule.times.map((time) => timeToString(time)) |
||||
.join(", "); |
||||
} |
||||
|
||||
const from = formatDateOfYear(schedule.from, <label>From </label>, editing); |
||||
const to = formatDateOfYear(schedule.to, <label>To </label>, editing); |
||||
return ( |
||||
<div> |
||||
<Form.Field inline> |
||||
<label>At</label> {times} |
||||
</Form.Field> |
||||
<WeekdaysView weekdays={schedule.weekdays} editing={editing} onChange={this.updateWeekdays}/> |
||||
{from} |
||||
{to} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
private updateWeekdays = (newWeekdays: Weekday[]) => { |
||||
this.props.schedule.weekdays = newWeekdays; |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
import * as moment from "moment"; |
||||
import * as React from "react"; |
||||
import { Form, Icon, Input, InputOnChangeData } from "semantic-ui-react"; |
||||
|
||||
import { DateOfYear } from "@common/sprinklersRpc"; |
||||
|
||||
const HTML_DATE_INPUT_FORMAT = "YYYY-MM-DD"; |
||||
|
||||
export interface ScheduleDateProps { |
||||
date: DateOfYear | null | undefined; |
||||
label: string | React.ReactNode | undefined; |
||||
editing: boolean | undefined; |
||||
onChange: (newDate: DateOfYear | null) => void; |
||||
} |
||||
|
||||
interface ScheduleDateState { |
||||
rawValue: string | ""; |
||||
lastDate: DateOfYear | null | undefined; |
||||
} |
||||
|
||||
export default class ScheduleDate extends React.Component<ScheduleDateProps, ScheduleDateState> { |
||||
static getDerivedStateFromProps(props: ScheduleDateProps, state: ScheduleDateState): Partial<ScheduleDateState> { |
||||
if (!DateOfYear.equals(props.date, state.lastDate)) { |
||||
const rawValue = props.date == null ? "" : |
||||
moment(props.date).format(HTML_DATE_INPUT_FORMAT); |
||||
return { lastDate: props.date, rawValue }; |
||||
} |
||||
return {}; |
||||
} |
||||
|
||||
constructor(p: ScheduleDateProps) { |
||||
super(p); |
||||
this.state = { rawValue: "", lastDate: undefined }; |
||||
} |
||||
|
||||
render() { |
||||
const { date, label, editing } = this.props; |
||||
|
||||
let dayNode: React.ReactNode; |
||||
if (editing) { // tslint:disable-line:prefer-conditional-expression
|
||||
let clearIcon: React.ReactNode | undefined; |
||||
if (date) { |
||||
clearIcon = <Icon name="ban" link onClick={this.onClear} />; |
||||
} |
||||
dayNode = <Input type="date" icon={clearIcon} value={this.state.rawValue} onChange={this.onChange}/>; |
||||
} else { |
||||
let dayString: string; |
||||
if (date) { |
||||
const format = (date.year === 0) ? "M/D" : "l"; |
||||
dayString = moment(date).format(format); |
||||
} else { |
||||
dayString = "N/A"; |
||||
} |
||||
dayNode = <span>{dayString}</span>; |
||||
} |
||||
|
||||
let labelNode: React.ReactNode = null; |
||||
if (typeof label === "string") { |
||||
labelNode = <label>{label}</label> |
||||
} else if (label != null) { |
||||
labelNode = label; |
||||
} |
||||
|
||||
return <Form.Field inline>{labelNode}{dayNode}</Form.Field>; |
||||
} |
||||
|
||||
private onChange = (e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData) => { |
||||
const { onChange } = this.props; |
||||
if (!onChange) return; |
||||
const m = moment(data.value, HTML_DATE_INPUT_FORMAT); |
||||
onChange(DateOfYear.fromMoment(m)); |
||||
} |
||||
|
||||
private onClear = () => { |
||||
const { onChange } = this.props; |
||||
if (!onChange) return; |
||||
onChange(null); |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
import * as moment from "moment"; |
||||
import * as React from "react"; |
||||
import { Form } from "semantic-ui-react"; |
||||
|
||||
import { TimeOfDay } from "@common/sprinklersRpc"; |
||||
import TimeInput from "./TimeInput"; |
||||
|
||||
function timeToString(time: TimeOfDay) { |
||||
return moment(time).format("LTS"); |
||||
} |
||||
|
||||
export default class ScheduleTimes extends React.Component<{ |
||||
times: TimeOfDay[]; |
||||
onChange: (newTimes: TimeOfDay[]) => void; |
||||
editing: boolean; |
||||
}> { |
||||
render() { |
||||
const { times, editing } = this.props; |
||||
let timesNode: React.ReactNode; |
||||
if (editing) { |
||||
timesNode = times |
||||
.map((time, i) => <TimeInput value={time} key={i} index={i} onChange={this.onTimeChange} />); |
||||
} else { |
||||
timesNode = ( |
||||
<span> |
||||
{times.map((time) => timeToString(time)).join(", ")} |
||||
</span> |
||||
); |
||||
} |
||||
return (<Form.Field inline className="scheduleTimes"> |
||||
<label>At</label> |
||||
{timesNode} |
||||
</Form.Field>); |
||||
} |
||||
private onTimeChange = (newTime: TimeOfDay, index: number) => { |
||||
const { times, onChange } = this.props; |
||||
const newTimes = times.slice(); |
||||
newTimes[index] = newTime; |
||||
onChange(newTimes); |
||||
} |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
import * as moment from "moment"; |
||||
import * as React from "react"; |
||||
import { Input, InputOnChangeData } from "semantic-ui-react"; |
||||
|
||||
import { TimeOfDay } from "@common/sprinklersRpc"; |
||||
|
||||
const HTML_TIME_INPUT_FORMAT = "HH:mm"; |
||||
|
||||
function timeOfDayToHtmlDateInput(tod: TimeOfDay): string { |
||||
return moment(tod).format(HTML_TIME_INPUT_FORMAT); |
||||
} |
||||
|
||||
export interface TimeInputProps { |
||||
value: TimeOfDay; |
||||
index: number; |
||||
onChange: (newValue: TimeOfDay, index: number) => void; |
||||
} |
||||
|
||||
export interface TimeInputState { |
||||
rawValue: string; |
||||
lastTime: TimeOfDay | null; |
||||
} |
||||
|
||||
export default class TimeInput extends React.Component<TimeInputProps, TimeInputState> { |
||||
static getDerivedStateFromProps(props: TimeInputProps, state: TimeInputState): Partial<TimeInputState> { |
||||
if (!TimeOfDay.equals(props.value, state.lastTime)) { |
||||
return { lastTime: props.value, rawValue: timeOfDayToHtmlDateInput(props.value) }; |
||||
} |
||||
return {}; |
||||
} |
||||
constructor(p: any) { |
||||
super(p); |
||||
this.state = { rawValue: "", lastTime: null }; |
||||
} |
||||
render() { |
||||
return <Input type="time" value={this.state.rawValue} onChange={this.onChange} onBlur={this.onBlur} />; |
||||
} |
||||
private onChange = (e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData) => { |
||||
this.setState({ |
||||
rawValue: data.value, |
||||
}); |
||||
}; |
||||
private onBlur: React.FocusEventHandler<HTMLInputElement> = (e) => { |
||||
const m = moment(this.state.rawValue, HTML_TIME_INPUT_FORMAT); |
||||
if (m.isValid()) { |
||||
this.props.onChange(TimeOfDay.fromMoment(m), this.props.index); |
||||
} else { |
||||
this.setState({ rawValue: timeOfDayToHtmlDateInput(this.props.value) }); |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
import * as React from "react"; |
||||
import { Checkbox, CheckboxProps, Form } from "semantic-ui-react"; |
||||
|
||||
import { Weekday, WEEKDAYS } from "@common/sprinklersRpc"; |
||||
|
||||
export interface WeekdaysViewProps { |
||||
weekdays: Weekday[]; |
||||
editing: boolean; |
||||
onChange?: (newWeekdays: Weekday[]) => void; |
||||
} |
||||
|
||||
export default class WeekdaysView extends React.Component<WeekdaysViewProps> { |
||||
render() { |
||||
const { weekdays, editing } = this.props; |
||||
let node: React.ReactNode; |
||||
if (editing) { |
||||
node = WEEKDAYS.map((weekday) => { |
||||
const checked = weekdays.find((wd) => wd === weekday) != null; |
||||
const name = Weekday[weekday]; |
||||
return ( |
||||
<Form.Field |
||||
control={Checkbox} |
||||
x-weekday={weekday} |
||||
label={name} |
||||
checked={checked} |
||||
key={weekday} |
||||
onChange={this.toggleWeekday} |
||||
/> |
||||
); |
||||
}); |
||||
} else { |
||||
node = weekdays.map((weekday) => Weekday[weekday]).join(", "); |
||||
} |
||||
return (<Form.Group inline> |
||||
<label>On</label> {node} |
||||
</Form.Group>); |
||||
} |
||||
private toggleWeekday = (event: React.FormEvent<HTMLInputElement>, data: CheckboxProps) => { |
||||
const { weekdays, onChange } = this.props; |
||||
if (!onChange) { |
||||
return; |
||||
} |
||||
const weekday: Weekday = Number(event.currentTarget.getAttribute("x-weekday")); |
||||
if (data.checked) { |
||||
const newWeekdays = weekdays.concat([weekday]); |
||||
newWeekdays.sort(); |
||||
onChange(newWeekdays); |
||||
} else { |
||||
onChange(weekdays.filter((wd) => wd !== weekday)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
import { observer } from "mobx-react"; |
||||
import * as React from "react"; |
||||
import { Form } from "semantic-ui-react"; |
||||
|
||||
import { DateOfYear, Schedule, TimeOfDay, Weekday } from "@common/sprinklersRpc"; |
||||
import ScheduleDate from "./ScheduleDate"; |
||||
import ScheduleTimes from "./ScheduleTimes"; |
||||
import WeekdaysView from "./WeekdaysView"; |
||||
|
||||
import "@app/styles/ScheduleView"; |
||||
|
||||
export interface ScheduleViewProps { |
||||
label?: string | React.ReactNode | undefined; |
||||
schedule: Schedule; |
||||
editing?: boolean; |
||||
} |
||||
|
||||
@observer |
||||
export default class ScheduleView extends React.Component<ScheduleViewProps> { |
||||
render() { |
||||
const { schedule, label } = this.props; |
||||
const editing = this.props.editing || false; |
||||
|
||||
let labelNode: React.ReactNode; |
||||
if (typeof label === "string") { |
||||
labelNode = <label>{label}</label> |
||||
} else if (label != null) { |
||||
labelNode = label; |
||||
} |
||||
|
||||
return ( |
||||
<Form.Field className="scheduleView"> |
||||
{labelNode} |
||||
<ScheduleTimes times={schedule.times} editing={editing} onChange={this.updateTimes}/> |
||||
<WeekdaysView weekdays={schedule.weekdays} editing={editing} onChange={this.updateWeekdays}/> |
||||
<ScheduleDate label="From" date={schedule.from} editing={editing} onChange={this.updateFromDate}/> |
||||
<ScheduleDate label="To" date={schedule.to} editing={editing} onChange={this.updateToDate}/> |
||||
</Form.Field> |
||||
); |
||||
} |
||||
|
||||
private updateTimes = (newTimes: TimeOfDay[]) => { |
||||
this.props.schedule.times = newTimes; |
||||
} |
||||
|
||||
private updateWeekdays = (newWeekdays: Weekday[]) => { |
||||
this.props.schedule.weekdays = newWeekdays; |
||||
} |
||||
|
||||
private updateFromDate = (newFromDate: DateOfYear | null) => { |
||||
this.props.schedule.from = newFromDate; |
||||
} |
||||
|
||||
private updateToDate = (newToDate: DateOfYear | null) => { |
||||
this.props.schedule.to = newToDate; |
||||
} |
||||
} |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
.scheduleView { |
||||
>.field, >.fields { |
||||
>label { |
||||
width: 2rem !important; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.scheduleTimes { |
||||
input { |
||||
margin: 0 .5rem; |
||||
} |
||||
} |
Loading…
Reference in new issue