Add task edit support.

master
Tom Hacohen 6 years ago
parent 09ddfe8be8
commit 4db3b24743

@ -86,6 +86,10 @@ class PimMain extends React.PureComponent<PropsType> {
);
} else if (this.state.tab === 1) {
this.newEvent();
} else if (this.state.tab === 2) {
this.props.history!.push(
routeResolver.getRoute('pim.tasks.new')
);
}
}
@ -143,7 +147,6 @@ class PimMain extends React.PureComponent<PropsType> {
<Fab
color="primary"
disabled={tab === 2}
style={style.floatingButton}
onClick={this.floatingButtonClicked}
>

@ -24,6 +24,7 @@ import JournalEntries from '../components/JournalEntries';
import ContactEdit from '../components/ContactEdit';
import Contact from '../components/Contact';
import EventEdit from '../components/EventEdit';
import TaskEdit from '../components/TaskEdit';
import Event from '../components/Event';
import PimMain from './PimMain';
@ -364,7 +365,7 @@ class Pim extends React.PureComponent {
routePrefix="pim.tasks"
collections={collectionsTaskList}
items={taskListItems}
componentEdit={undefined}
componentEdit={TaskEdit}
componentView={Event}
onItemSave={this.onItemSave}
onItemDelete={this.onItemDelete}

@ -0,0 +1,341 @@
import * as React from 'react';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import InputLabel from '@material-ui/core/InputLabel';
import * as colors from '@material-ui/core/colors';
import IconDelete from '@material-ui/icons/Delete';
import IconCancel from '@material-ui/icons/Clear';
import IconSave from '@material-ui/icons/Save';
import DateTimePicker from '../widgets/DateTimePicker';
import ConfirmationDialog from '../widgets/ConfirmationDialog';
import { Location } from 'history';
import { withRouter } from 'react-router';
import * as uuid from 'uuid';
import * as ICAL from 'ical.js';
import * as EteSync from '../api/EteSync';
import { TaskType } from '../pim-types';
interface PropsType {
collections: Array<EteSync.CollectionInfo>;
initialCollection?: string;
item?: TaskType;
onSave: (item: TaskType, journalUid: string, originalItem?: TaskType) => void;
onDelete: (item: TaskType, journalUid: string) => void;
onCancel: () => void;
location: Location;
};
class TaskEdit extends React.PureComponent<PropsType> {
state: {
uid: string,
title: string;
allDay: boolean;
start?: Date;
due?: Date;
location: string;
description: string;
journalUid: string;
error?: string;
showDeleteDialog: boolean;
};
constructor(props: any) {
super(props);
this.state = {
uid: '',
title: '',
allDay: false,
location: '',
description: '',
journalUid: '',
showDeleteDialog: false,
};
if (this.props.item !== undefined) {
const event = this.props.item;
this.state.uid = event.uid;
this.state.title = event.title ? event.title : '';
if (event.startDate) {
this.state.allDay = event.startDate.isDate;
this.state.start = event.startDate.toJSDate();
}
if (event.dueDate) {
this.state.due = event.dueDate.toJSDate();
}
this.state.location = event.location ? event.location : '';
this.state.description = event.description ? event.description : '';
} else {
this.state.uid = uuid.v4();
}
if (props.initialCollection) {
this.state.journalUid = props.initialCollection;
} else if (props.collections[0]) {
this.state.journalUid = props.collections[0].uid;
}
this.onSubmit = this.onSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.toggleAllDay = this.toggleAllDay.bind(this);
this.onDeleteRequest = this.onDeleteRequest.bind(this);
}
componentWillReceiveProps(nextProps: any) {
if ((this.props.collections !== nextProps.collections) ||
(this.props.initialCollection !== nextProps.initialCollection)) {
if (nextProps.initialCollection) {
this.state.journalUid = nextProps.initialCollection;
} else if (nextProps.collections[0]) {
this.state.journalUid = nextProps.collections[0].uid;
}
}
}
handleChange(name: string, value: string) {
this.setState({
[name]: value
});
}
handleInputChange(event: React.ChangeEvent<any>) {
const name = event.target.name;
const value = event.target.value;
this.handleChange(name, value);
}
toggleAllDay() {
this.setState({allDay: !this.state.allDay});
}
onSubmit(e: React.FormEvent<any>) {
e.preventDefault();
function fromDate(date: Date | undefined, allDay: boolean) {
if (!date) {
return undefined;
}
const ret = ICAL.Time.fromJSDate(date, false);
if (!allDay) {
return ret;
} else {
let data = ret.toJSON();
data.isDate = allDay;
return ICAL.Time.fromData(data);
}
}
const startDate = fromDate(this.state.start, this.state.allDay);
const dueDate = fromDate(this.state.due, this.state.allDay);
if (startDate && dueDate) {
if (startDate.compare(dueDate) >= 0) {
this.setState({error: 'End time must be later than start time!'});
return;
}
}
let event = (this.props.item) ?
this.props.item.clone()
:
new TaskType(null)
;
event.uid = this.state.uid;
event.summary = this.state.title;
if (startDate) {
event.startDate = startDate;
}
event.dueDate = dueDate;
event.location = this.state.location;
event.description = this.state.description;
event.component.updatePropertyWithValue('last-modified', ICAL.Time.now());
this.props.onSave(event, this.state.journalUid, this.props.item);
}
onDeleteRequest() {
this.setState({
showDeleteDialog: true
});
}
render() {
const styles = {
form: {
},
fullWidth: {
width: '100%',
boxSizing: 'border-box' as any,
marginTop: 16,
},
submit: {
marginTop: 40,
marginBottom: 20,
textAlign: 'right' as any,
},
};
const recurring = this.props.item && this.props.item.isRecurring();
return (
<React.Fragment>
<h2>
{this.props.item ? 'Edit Task' : 'New Task'}
</h2>
{recurring && (
<div>
<span style={{ color: 'red' }}>IMPORTANT: </span>
This is a recurring event, for now, only editing the whole series
(by editing the first instance) is supported.
</div>
)}
{this.state.error && (
<div>ERROR! {this.state.error}</div>
)}
<form style={styles.form} onSubmit={this.onSubmit}>
<TextField
name="title"
placeholder="Enter title"
style={styles.fullWidth}
value={this.state.title}
onChange={this.handleInputChange}
/>
<FormControl disabled={this.props.item !== undefined} style={styles.fullWidth} >
<InputLabel>
Saving to
</InputLabel>
<Select
name="journalUid"
value={this.state.journalUid}
disabled={this.props.item !== undefined}
onChange={this.handleInputChange}
>
{this.props.collections.map((x) => (
<MenuItem key={x.uid} value={x.uid}>{x.displayName}</MenuItem>
))}
</Select>
</FormControl>
<FormGroup>
<FormControlLabel
control={
<Switch
name="allDay"
checked={this.state.allDay}
onChange={this.toggleAllDay}
color="primary"
/>
}
label="All Day"
/>
</FormGroup>
<div>
<DateTimePicker
dateOnly={this.state.allDay}
placeholder="Start"
value={this.state.start}
onChange={(date?: Date) => this.setState({start: date})}
/>
</div>
<div>
<DateTimePicker
dateOnly={this.state.allDay}
placeholder="End"
value={this.state.due}
onChange={(date?: Date) => this.setState({due: date})}
/>
</div>
<TextField
name="location"
placeholder="Add location"
style={styles.fullWidth}
value={this.state.location}
onChange={this.handleInputChange}
/>
<TextField
name="description"
placeholder="Add description"
multiline={true}
style={styles.fullWidth}
value={this.state.description}
onChange={this.handleInputChange}
/>
<div style={styles.submit}>
<Button
variant="contained"
onClick={this.props.onCancel}
>
<IconCancel style={{marginRight: 8}} />
Cancel
</Button>
{this.props.item &&
<Button
variant="contained"
style={{marginLeft: 15, backgroundColor: colors.red[500], color: 'white'}}
onClick={this.onDeleteRequest}
>
<IconDelete style={{marginRight: 8}} />
Delete
</Button>
}
<Button
type="submit"
variant="contained"
color="secondary"
style={{marginLeft: 15}}
>
<IconSave style={{marginRight: 8}} />
Save
</Button>
</div>
<div>
Not all types are supported at the moment. If you are editing a contact,
the unsupported types will be copied as is.
</div>
</form>
<ConfirmationDialog
title="Delete Confirmation"
labelOk="Delete"
open={this.state.showDeleteDialog}
onOk={() => this.props.onDelete(this.props.item!, this.props.initialCollection!)}
onCancel={() => this.setState({showDeleteDialog: false})}
>
Are you sure you would like to delete this event?
</ConfirmationDialog>
</React.Fragment>
);
}
}
export default withRouter(TaskEdit);

@ -60,11 +60,27 @@ export class TaskType extends EventType {
return new TaskType(comp.getFirstSubcomponent('vtodo'));
}
constructor(comp: ICAL.Component | null) {
super(comp ? comp : new ICAL.Component('vtodo'));
}
get completed() {
const status = this.component.getFirstPropertyValue('status');
return status === 'COMPLETED';
}
set dueDate(date: ICAL.Time | undefined) {
if (date) {
this.component.updatePropertyWithValue('due', date);
} else {
this.component.removeAllProperties('due');
}
}
get dueDate() {
return this.component.getFirstPropertyValue('due');
}
clone() {
const ret = new TaskType(new ICAL.Component(this.component.toJSON()));
ret.color = this.color;

Loading…
Cancel
Save