Tasks: batched uploads for recurring task completion

master
Andrew P Maney 5 years ago committed by Tom Hacohen
parent de94a02025
commit 508d02a0ea

@ -25,6 +25,8 @@ import { EventType, ContactType, TaskType, PimType } from '../pim-types';
import { routeResolver } from '../App';
import { historyPersistor } from '../persist-state-history';
import { SyncInfo } from '../SyncGate';
import { UserInfoData, CredentialsData } from '../store';
const addressBookTitle = 'Address Book';
const calendarTitle = 'Calendar';
@ -41,6 +43,9 @@ interface PropsType {
theme: Theme;
collectionsTaskList: EteSync.CollectionInfo[];
onItemSave: (item: PimType, journalUid: string, originalItem?: PimType) => Promise<void>;
syncInfo: SyncInfo;
userInfo: UserInfoData;
etesync: CredentialsData;
}
class PimMain extends React.PureComponent<PropsType> {
@ -153,6 +158,9 @@ class PimMain extends React.PureComponent<PropsType> {
collections={this.props.collectionsTaskList}
onItemClick={this.taskClicked}
onItemSave={this.props.onItemSave}
syncInfo={this.props.syncInfo}
userInfo={this.props.userInfo}
etesync={this.props.etesync}
/>
}
</Container>

@ -329,6 +329,9 @@ class Pim extends React.PureComponent {
history={history}
onItemSave={this.onItemSave}
collectionsTaskList={collectionsTaskList}
syncInfo={this.props.syncInfo}
userInfo={this.props.userInfo}
etesync={this.props.etesync}
/>
)}
/>

@ -6,14 +6,14 @@ import * as React from 'react';
import * as EteSync from 'etesync';
import { List } from '../../widgets/List';
import Toast from '../../widgets/Toast';
import Toast, { PropsType as ToastProps } from '../../widgets/Toast';
import { TaskType, PimType, TaskStatusType } from '../../pim-types';
import Divider from '@material-ui/core/Divider';
import Grid from '@material-ui/core/Grid';
import { useTheme, makeStyles } from '@material-ui/core/styles';
import { useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import Fuse from 'fuse.js';
@ -22,8 +22,12 @@ import Sidebar from './Sidebar';
import Toolbar from './Toolbar';
import QuickAdd from './QuickAdd';
import { StoreState } from '../../store';
import { StoreState, UserInfoData, CredentialsData } from '../../store';
import { formatDate } from '../../helpers';
import { SyncInfo } from '../../SyncGate';
import { fetchEntries } from '../../store/actions';
import { Action } from 'redux-actions';
import { addJournalEntries } from '../../etesync-helpers';
function sortCompleted(a: TaskType, b: TaskType) {
return (!!a.finished === !!b.finished) ? 0 : (a.finished) ? 1 : -1;
@ -104,30 +108,71 @@ interface PropsType {
collections: EteSync.CollectionInfo[];
onItemClick: (entry: TaskType) => void;
onItemSave: (item: PimType, journalUid: string, originalItem?: PimType) => Promise<void>;
syncInfo: SyncInfo;
userInfo: UserInfoData;
etesync: CredentialsData;
}
export default function TaskList(props: PropsType) {
const [showCompleted, setShowCompleted] = React.useState(false);
const [showHidden, setShowHidden] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState('');
const [info, setInfo] = React.useState('');
const [toast, setToast] = React.useState<{ message: string, severity: ToastProps['severity'] }>({ message: '', severity: undefined });
const settings = useSelector((state: StoreState) => state.settings.taskSettings);
const { filterBy, sortBy } = settings;
const theme = useTheme();
const classes = useStyles();
const dispatch = useDispatch();
const handleToggleComplete = async (task: TaskType, completed: boolean) => {
const handleToggleComplete = (task: TaskType, completed: boolean) => {
const clonedTask = task.clone();
clonedTask.status = completed ? TaskStatusType.Completed : TaskStatusType.NeedsAction;
const nextTask = completed ? task.getNextOccurence() : null;
await props.onItemSave(clonedTask, (task as any).journalUid, task);
const syncJournal = props.syncInfo.get((task as any).journalUid);
if (nextTask) {
await props.onItemSave(nextTask, (task as any).journalUid);
setInfo(`${nextTask.title} rescheduled for ${formatDate(nextTask.startDate ?? nextTask.dueDate)}`);
if (syncJournal === undefined) {
setToast({ message: 'Could not sync.', severity: 'error' });
return;
}
const journal = syncJournal.journal;
let prevUid: string | null = null;
let last = syncJournal.journalEntries.last() as EteSync.Entry;
if (last) {
prevUid = last.uid;
}
dispatch<any>(fetchEntries(props.etesync, journal.uid, prevUid))
.then((entriesAction: Action<EteSync.Entry[]>) => {
last = entriesAction.payload!.slice(-1).pop() as EteSync.Entry;
if (last) {
prevUid = last.uid;
}
const changeTask = [EteSync.SyncEntryAction.Change, clonedTask.toIcal()];
const updates = [];
updates.push(changeTask as [EteSync.SyncEntryAction, string]);
if (nextTask) {
const addNextTask = [EteSync.SyncEntryAction.Add, nextTask.toIcal()];
updates.push(addNextTask as [EteSync.SyncEntryAction, string]);
}
return dispatch(addJournalEntries(props.etesync, props.userInfo, journal, prevUid, updates));
})
.then(() => {
if (nextTask) {
setToast({ message: `${nextTask.title} rescheduled for ${formatDate(nextTask.startDate ?? nextTask.dueDate)}`, severity: 'success' });
}
})
.catch(() => {
setToast({ message: 'Failed to save changes. This may be due to a network error.', severity: 'error' });
});
};
const potentialEntries = React.useMemo(
@ -212,8 +257,8 @@ export default function TaskList(props: PropsType) {
</List>
</Grid>
<Toast open={!!info} severity="info" onClose={() => setInfo('')} autoHideDuration={3000}>
{info}
<Toast open={!!toast.message} severity={toast.severity} onClose={() => setToast({ message: '', severity: undefined })} autoHideDuration={3000}>
{toast.message}
</Toast>
</Grid>
);

@ -49,3 +49,25 @@ export function addJournalEntry(
const entry = createJournalEntry(etesync, userInfo, journal, prevUid, action, content);
return addEntries(etesync, journal.uid, [entry], prevUid);
}
/**
* Adds multiple journal entries and uploads them all at once
* @param updates list of tuples with shape (action, content)
*/
export function addJournalEntries(
etesync: CredentialsData,
userInfo: UserInfoData,
journal: EteSync.Journal,
lastUid: string | null,
updates: [EteSync.SyncEntryAction, string][]) {
let prevUid = lastUid;
const entries = updates.map(([action, content]) => {
const entry = createJournalEntry(etesync, userInfo, journal, prevUid, action, content);
prevUid = entry.uid;
return entry;
});
return addEntries(etesync, journal.uid, entries, lastUid);
}

@ -6,7 +6,7 @@ import * as React from 'react';
import Snackbar from '@material-ui/core/Snackbar';
import Alert from '@material-ui/lab/Alert';
interface PropsType {
export interface PropsType {
open: boolean;
children: React.ReactNode;
onClose?: (event?: React.SyntheticEvent, reason?: string) => void;
@ -19,7 +19,7 @@ export default function Toast(props: PropsType) {
return (
<Snackbar open={open} onClose={onClose} autoHideDuration={autoHideDuration}>
<Alert severity={severity ?? 'error'} variant="filled" elevation={6} onClose={onClose}>
<Alert severity={severity} variant="filled" elevation={6} onClose={onClose}>
{children}
</Alert>
</Snackbar>

Loading…
Cancel
Save