widgets: add RRule widget
First step towards fixing #50 RRule widget for repeating tasks and calendar eventsmaster
parent
c6eafadb8b
commit
6446777c79
@ -0,0 +1,293 @@
|
||||
import * as React from 'react';
|
||||
import Container from './Container';
|
||||
import { TextField, Select, MenuItem, FormGroup, FormControlLabel, Checkbox, InputLabel, FormControl } from '@material-ui/core';
|
||||
import DateTimePicker from '../widgets/DateTimePicker';
|
||||
import { isNumber } from 'util';
|
||||
|
||||
interface PropsType {
|
||||
onChange: (rrule: RRuleOptions) => void;
|
||||
rrule: RRuleOptions;
|
||||
}
|
||||
export interface RRuleOptions {
|
||||
freq: Frequency;
|
||||
interval: number;
|
||||
until?: Date;
|
||||
count?: number;
|
||||
byweekday?: Weekday[];
|
||||
bymonthday?: number;
|
||||
byyearday?: number;
|
||||
byweekno?: number;
|
||||
bymonth?: Months;
|
||||
bysetpos?: number;
|
||||
}
|
||||
|
||||
enum Frequency {
|
||||
Year = 0,
|
||||
Month = 1,
|
||||
Week = 2,
|
||||
Day = 3,
|
||||
}
|
||||
enum Ends {
|
||||
Never,
|
||||
Date,
|
||||
After,
|
||||
}
|
||||
enum Months {
|
||||
Jan = 1,
|
||||
Feb,
|
||||
Mar,
|
||||
Apr,
|
||||
May,
|
||||
Jun,
|
||||
Jul,
|
||||
Aug,
|
||||
Sep,
|
||||
Oct,
|
||||
Nov,
|
||||
Dec,
|
||||
}
|
||||
enum MonthRepeat {
|
||||
Bysetpos,
|
||||
Bymonthday,
|
||||
}
|
||||
enum Weekday {
|
||||
Mo,
|
||||
Tu,
|
||||
We,
|
||||
Th,
|
||||
Fr,
|
||||
Sa,
|
||||
Su
|
||||
}
|
||||
|
||||
const menuItemsMonths = Object.keys(Months).filter((key) => Number(key)).map((key) => {
|
||||
return (
|
||||
<MenuItem key={key} value={key}>{Months[key]}</MenuItem>
|
||||
);
|
||||
});
|
||||
const menuItemsEnds = [Ends.Never, Ends.Date, Ends.After].map((key) => {
|
||||
return (
|
||||
<MenuItem key={key} value={key}>{Ends[key]}</MenuItem>
|
||||
);
|
||||
});
|
||||
const weekdays = [Weekday.Mo, Weekday.Tu, Weekday.We, Weekday.Th, Weekday.Fr, Weekday.Sa, Weekday.Su];
|
||||
const menuItemsFrequency = [Frequency.Year, Frequency.Month, Frequency.Week, Frequency.Day].map((value) => {
|
||||
return (
|
||||
<MenuItem key={value} value={value}>{Frequency[value]}</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
export default function RRuleEteSync(props: PropsType) {
|
||||
const options = props.rrule;
|
||||
|
||||
function updateRule(newOptions: Partial<RRuleOptions>): void {
|
||||
const updatedOptions = { ...options, ...newOptions };
|
||||
props.onChange(updatedOptions);
|
||||
}
|
||||
|
||||
function getEnds(): Ends {
|
||||
if (options.until && !options.count) {
|
||||
return Ends.Date;
|
||||
} else if (!options.until && options.count) {
|
||||
return Ends.After;
|
||||
} else {
|
||||
return Ends.Never;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheckboxWeekday(event: React.FormEvent<{ value: unknown }>): void {
|
||||
const checkbox = event.target as HTMLInputElement;
|
||||
const weekday = Number(checkbox.value);
|
||||
let byweekdayArray = options.byweekday as Weekday[];
|
||||
let byweekday;
|
||||
if (!checkbox.checked && byweekdayArray) {
|
||||
byweekday = byweekdayArray.filter((day) => day !== weekday);
|
||||
} else if (byweekdayArray) {
|
||||
byweekdayArray = byweekdayArray.filter((day) => day !== weekday);
|
||||
byweekday = [...byweekdayArray, weekday];
|
||||
} else {
|
||||
byweekday = [weekday];
|
||||
}
|
||||
updateRule({ byweekday: byweekday });
|
||||
}
|
||||
|
||||
function isWeekdayChecked(day: number): boolean {
|
||||
const weekdayArray = options.byweekday;
|
||||
if (weekdayArray) {
|
||||
return isNumber(weekdayArray.find((value) => Weekday[value] === Weekday[day]));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const checkboxWeekDays = weekdays.map((_, index) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
key={index}
|
||||
value={index}
|
||||
checked={isWeekdayChecked(index)}
|
||||
onChange={handleCheckboxWeekday}
|
||||
/>}
|
||||
key={index}
|
||||
label={Weekday[index]} />
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<FormControlLabel
|
||||
value={options.freq}
|
||||
label="Repeat every :"
|
||||
labelPlacement="start"
|
||||
control={<TextField
|
||||
type="number"
|
||||
inputProps={{ min: 1, max: 1000 }}
|
||||
value={options.interval}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
event.preventDefault();
|
||||
const inputNode = event.currentTarget as HTMLInputElement;
|
||||
if (inputNode.value === '') {
|
||||
updateRule({ interval: undefined });
|
||||
} else if (inputNode.valueAsNumber) {
|
||||
updateRule({ interval: inputNode.valueAsNumber });
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
/>
|
||||
<Select
|
||||
value={options.freq}
|
||||
style={{ alignSelf: 'flex-end', marginLeft: 20 }}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
const freq = Number((event.target as HTMLSelectElement).value);
|
||||
const updatedOptions = {
|
||||
freq: freq,
|
||||
bysetpos: undefined,
|
||||
bymonthday: freq === Frequency.Month || Frequency.Year === freq ? 1 : undefined,
|
||||
byweekday: undefined,
|
||||
bymonth: freq === Frequency.Year ? Months.Jan : undefined,
|
||||
};
|
||||
updateRule(updatedOptions);
|
||||
}}
|
||||
>
|
||||
{menuItemsFrequency}
|
||||
</Select>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
|
||||
{(options.freq === Frequency.Month) &&
|
||||
<Select
|
||||
value={options.bysetpos ? MonthRepeat.Bysetpos : MonthRepeat.Bymonthday}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
const value = Number((event.target as HTMLInputElement).value);
|
||||
if (value === MonthRepeat.Bymonthday) {
|
||||
updateRule({ bymonthday: 1, bysetpos: undefined, bymonth: Months.Jan });
|
||||
} else if (value === MonthRepeat.Bysetpos) {
|
||||
updateRule({ bysetpos: 1, bymonthday: undefined, bymonth: undefined });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value={MonthRepeat.Bymonthday}>On</MenuItem>
|
||||
<MenuItem value={MonthRepeat.Bysetpos}>On the</MenuItem>
|
||||
</Select>
|
||||
}
|
||||
|
||||
|
||||
{options.bysetpos &&
|
||||
<Select
|
||||
value={options.bysetpos}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
updateRule({ bysetpos: Number((event.target as HTMLInputElement).value) });
|
||||
}}>
|
||||
<MenuItem value={1}>First</MenuItem>
|
||||
<MenuItem value={2}>Second</MenuItem>
|
||||
<MenuItem value={3}>Third</MenuItem>
|
||||
<MenuItem value={4}>Fourth</MenuItem>
|
||||
<MenuItem value={-1}>Last</MenuItem>
|
||||
</Select>
|
||||
}
|
||||
{(options.freq === Frequency.Year && options.bymonth) &&
|
||||
<Select
|
||||
value={options.bymonth}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
updateRule({ bymonth: Number((event.target as HTMLInputElement).value) });
|
||||
}}>
|
||||
{menuItemsMonths}
|
||||
</Select>
|
||||
}
|
||||
{options.bymonthday &&
|
||||
<TextField
|
||||
type="number"
|
||||
value={options.bymonthday}
|
||||
label="Month day"
|
||||
style={{ width: 100 }}
|
||||
inputProps={{ min: 1, step: 1, max: 31 }}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
event.preventDefault();
|
||||
const value = (event.currentTarget as HTMLInputElement).value;
|
||||
const numberValue = Number(value);
|
||||
if (value === '') {
|
||||
updateRule({ bymonthday: undefined });
|
||||
} else if (numberValue < 32 && numberValue > 0) {
|
||||
updateRule({ bymonthday: numberValue });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
{options.freq !== Frequency.Day &&
|
||||
<FormGroup row>{checkboxWeekDays}</FormGroup>
|
||||
}
|
||||
<FormControl>
|
||||
<InputLabel>Ends</InputLabel>
|
||||
<Select
|
||||
value={getEnds()}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
const value = Number((event.target as HTMLSelectElement).value);
|
||||
let updateOptions;
|
||||
if (value === Ends.Date) {
|
||||
updateOptions = { count: undefined, until: new Date() };
|
||||
} else if (value === Ends.After) {
|
||||
updateOptions = { until: undefined, count: 1 };
|
||||
} else {
|
||||
updateOptions = { count: undefined, until: undefined };
|
||||
}
|
||||
updateRule(updateOptions);
|
||||
}}>
|
||||
{menuItemsEnds}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{options.until &&
|
||||
<DateTimePicker
|
||||
dateOnly
|
||||
value={options.until || undefined}
|
||||
placeholder="Ends"
|
||||
onChange={(date?: Date) => updateRule({ until: date })}
|
||||
/>
|
||||
}
|
||||
{options.count &&
|
||||
<TextField
|
||||
type="number"
|
||||
value={options.count}
|
||||
label="Count"
|
||||
style={{ width: 60 }}
|
||||
inputProps={{ min: 1, step: 1 }}
|
||||
onChange={(event: React.FormEvent<{ value: unknown }>) => {
|
||||
event.preventDefault();
|
||||
const inputNode = event.currentTarget as HTMLInputElement;
|
||||
if (inputNode.value === '') {
|
||||
updateRule({ count: 1 });
|
||||
} else if (inputNode.valueAsNumber) {
|
||||
updateRule({ count: inputNode.valueAsNumber });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue