diff --git a/src/ContactEdit.tsx b/src/ContactEdit.tsx new file mode 100644 index 0000000..dc2f67d --- /dev/null +++ b/src/ContactEdit.tsx @@ -0,0 +1,299 @@ +import * as React from 'react'; +import IconButton from 'material-ui/IconButton'; +import RaisedButton from 'material-ui/RaisedButton'; +import TextField from 'material-ui/TextField'; +import SelectField from 'material-ui/SelectField'; +import MenuItem from 'material-ui/MenuItem'; + +import IconAdd from 'material-ui/svg-icons/content/add'; +import IconClear from 'material-ui/svg-icons/content/clear'; + +import * as uuid from 'uuid'; +import * as ICAL from 'ical.js'; + +import * as EteSync from './api/EteSync'; + +import { ContactType } from './pim-types'; + +const TypeSelector = (props: any) => { + const types = [ + {type: 'Home'}, + {type: 'Work'}, + {type: 'Other'}, + ]; + + return ( + + {types.map((x) => ( + + ))} + + ); +}; + +class ValueType { + type: string; + value: string; + + constructor(type?: string, value?: string) { + this.type = type ? type : 'home'; + this.value = value ? value : ''; + } +} + +interface ValueTypeComponentProps { + type?: string; + style?: object; + + name: string; + hintText: string; + value: ValueType; + onClearRequest: (name: string) => void; + onChange: (name: string, type: string, value: string) => void; +} + +const ValueTypeComponent = (props: ValueTypeComponentProps) => { + return ( + + props.onChange(props.name, props.value.type, event.target.value)} + /> + props.onClearRequest(props.name)} + tooltip="Remove" + > + + + props.onChange(props.name, payload, props.value.value) + } + /> + + ); +}; + +class ContactEdit extends React.Component { + state: { + uid: string, + fn: string; + phones: ValueType[]; + emails: ValueType[]; + + journalUid: string; + }; + + props: { + collections: Array, + initialCollection?: string, + contact?: ContactType, + onSave: (contact: ContactType, journalUid: string, originalContact?: ContactType) => void; + }; + + constructor(props: any) { + super(props); + this.state = { + uid: '', + fn: '', + phones: [new ValueType()], + emails: [new ValueType()], + + journalUid: '', + }; + if (this.props.contact !== undefined) { + const contact = this.props.contact; + + this.state.uid = contact.uid; + this.state.fn = contact.fn ? contact.fn : ''; + } else { + this.state.uid = uuid.v4(); + } + + 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.handleValueTypeChange = this.handleValueTypeChange.bind(this); + this.addValueType = this.addValueType.bind(this); + this.removeValueType = this.removeValueType.bind(this); + } + + componentWillReceiveProps(nextProps: any) { + if (this.props.collections !== nextProps.collections) { + if (nextProps.collections[0]) { + this.setState({journalUid: nextProps.collections[0].uid}); + } + } + } + + addValueType(name: string) { + this.setState((prevState, props) => { + let newArray = prevState[name].slice(0); + newArray.push(new ValueType()); + return { + ...prevState, + [name]: newArray, + }; + }); + } + + removeValueType(name: string, idx: number) { + this.setState((prevState, props) => { + let newArray = prevState[name].slice(0); + newArray.splice(idx, 1); + return { + ...prevState, + [name]: newArray, + }; + }); + } + + handleValueTypeChange(name: string, idx: number, value: ValueType) { + this.setState((prevState, props) => { + let newArray = prevState[name].slice(0); + newArray[idx] = value; + return { + ...prevState, + [name]: newArray, + }; + }); + } + + handleChange(name: string, value: string) { + this.setState({ + [name]: value + }); + + } + + handleInputChange(contact: any) { + const name = contact.target.name; + const value = contact.target.value; + this.handleChange(name, value); + } + + onSubmit(e: any) { + e.prcontactDefault(); + + let contact = new ContactType(new ICAL.Component(['vcard', []])); + // contact.uid = this.state.uid; + // contact.fn = this.state.fn; + + this.props.onSave(contact, this.state.journalUid, this.props.contact); + } + + render() { + const styles = { + form: { + }, + fullWidth: { + width: '100%', + boxSizing: 'border-box', + }, + submit: { + marginTop: 40, + textAlign: 'right', + }, + }; + + return ( + +

+ {this.props.contact ? 'Edit Contact' : 'New Contact'} +

+
+ this.handleChange('journalUid', payload)} + > + {this.props.collections.map((x) => ( + + ))} + + + + +
+ Phone numbers + this.addValueType('phones')} + tooltip="Add phone number" + > + + +
+ {this.state.phones.map((x, idx) => ( + this.removeValueType(name, idx)} + onChange={(name: string, type: string, value: string) => ( + this.handleValueTypeChange(name, idx, {type, value}) + )} + /> + ))} + +
+ Emails + this.addValueType('emails')} + tooltip="Add email address" + > + + +
+ {this.state.emails.map((x, idx) => ( + this.removeValueType(name, idx)} + onChange={(name: string, type: string, value: string) => ( + this.handleValueTypeChange(name, idx, {type, value}) + )} + /> + ))} + +
+ +
+ +
+ Not all types are supported at the moment. If you are editing a contact, + the unsupported types will be copied as is. +
+ +
+ ); + } +} + +export default ContactEdit; diff --git a/src/Pim.tsx b/src/Pim.tsx index 378a89d..7b139f8 100644 --- a/src/Pim.tsx +++ b/src/Pim.tsx @@ -5,10 +5,11 @@ import IconEdit from 'material-ui/svg-icons/editor/mode-edit'; import * as EteSync from './api/EteSync'; -import { EventType } from './pim-types'; +import { ContactType, EventType } from './pim-types'; import Container from './Container'; +import ContactEdit from './ContactEdit'; import Contact from './Contact'; import EventEdit from './EventEdit'; import Event from './Event'; @@ -61,9 +62,31 @@ class Pim extends React.Component { }); } + onContactSave(contact: ContactType, journalUid: string, originalContact?: ContactType) { + const journal = this.props.journals.find((x) => (x.uid === journalUid)); + + if (journal === undefined) { + return; + } + + const entries = this.props.entries[journal.uid]; + + if (entries.value === null) { + return; + } + + let action = (originalContact === undefined) ? EteSync.SyncEntryAction.Add : EteSync.SyncEntryAction.Change; + let saveContact = store.dispatch( + createJournalEntry(this.props.etesync, journal, entries.value, action, contact.toIcal())); + (saveContact as any).then(() => { + this.props.history.goBack(); + }); + } + render() { const derived = this.props.etesync.encryptionKey; + let collectionsAddressBook: Array = []; let collectionsCalendar: Array = []; let syncEntriesCalendar = []; let syncEntriesAddressBook = []; @@ -93,6 +116,7 @@ class Pim extends React.Component { if (collectionInfo.type === 'ADDRESS_BOOK') { syncEntriesAddressBook.push(syncEntriesToItemMap(collectionInfo, syncEntries)); + collectionsAddressBook.push(collectionInfo); } else if (collectionInfo.type === 'CALENDAR') { syncEntriesCalendar.push(syncEntriesToCalendarItemMap(collectionInfo, syncEntries)); collectionsCalendar.push(collectionInfo); @@ -117,10 +141,44 @@ class Pim extends React.Component { )} /> ( + + + + )} + /> + ( + + + + )} + /> + ( +
+ } + onClick={() => + history.push(routeResolver.getRoute( + 'pim.contacts._id.edit', + {contactUid: match.params.contactUid})) + } + /> +
)} diff --git a/src/pim-types.tsx b/src/pim-types.tsx index c252469..29d9eda 100644 --- a/src/pim-types.tsx +++ b/src/pim-types.tsx @@ -35,6 +35,10 @@ export class ContactType { this.comp = comp; } + toIcal() { + return this.comp.toString(); + } + get uid() { return this.comp.getFirstPropertyValue('uid'); }