Create GroupEdit

master
Ramzan 4 years ago
parent 2505650708
commit 6ab60f24ef

@ -41,7 +41,10 @@ export const routeResolver = new RouteResolver({
contacts: { contacts: {
_id: { _id: {
_base: ":itemUid", _base: ":itemUid",
edit: "edit", edit: {
contact: "contact",
group: "group",
},
log: "log", log: "log",
}, },
new: { new: {

@ -121,12 +121,11 @@ interface PropsType {
collections: CachedCollection[]; collections: CachedCollection[];
initialCollection?: string; initialCollection?: string;
item?: ContactType; item?: ContactType;
newGroup?: boolean;
onSave: (contact: ContactType, collectionUid: string, originalContact?: ContactType) => Promise<void>; onSave: (contact: ContactType, collectionUid: string, originalContact?: ContactType) => Promise<void>;
onDelete: (contact: ContactType, collectionUid: string) => void; onDelete: (contact: ContactType, collectionUid: string) => void;
onCancel: () => void; onCancel: () => void;
history: History<any>; history: History<any>;
groups: ContactType[]; allGroups: ContactType[];
} }
class ContactEdit extends React.PureComponent<PropsType> { class ContactEdit extends React.PureComponent<PropsType> {
@ -145,13 +144,12 @@ class ContactEdit extends React.PureComponent<PropsType> {
org: string; org: string;
note: string; note: string;
title: string; title: string;
group: boolean;
collectionUid: string; collectionUid: string;
showDeleteDialog: boolean; showDeleteDialog: boolean;
collectionGroups: ContactType[]; collectionGroups: {};
newGroups: (string | ContactType)[]; newGroups: string[];
originalGroups: ContactType[]; originalGroups: string[];
}; };
constructor(props: PropsType) { constructor(props: PropsType) {
@ -171,11 +169,10 @@ class ContactEdit extends React.PureComponent<PropsType> {
org: "", org: "",
note: "", note: "",
title: "", title: "",
group: false,
collectionUid: "", collectionUid: "",
showDeleteDialog: false, showDeleteDialog: false,
collectionGroups: [], collectionGroups: {},
newGroups: [], newGroups: [],
originalGroups: [], originalGroups: [],
}; };
@ -183,7 +180,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
if (this.props.item !== undefined) { if (this.props.item !== undefined) {
const contact = this.props.item; const contact = this.props.item;
this.state.group = contact.group;
this.state.uid = contact.uid; this.state.uid = contact.uid;
this.state.fn = contact.fn ? contact.fn : ""; this.state.fn = contact.fn ? contact.fn : "";
if (contact.n) { if (contact.n) {
@ -244,16 +240,17 @@ class ContactEdit extends React.PureComponent<PropsType> {
this.state.collectionUid = props.collections[0].collection.uid; this.state.collectionUid = props.collections[0].collection.uid;
} }
this.state.collectionGroups = this.props.groups.filter((group) => this.state.collectionUid === group.collectionUid); this.state.collectionGroups = this.getCollectionGroups(this.state.collectionUid);
this.state.collectionGroups.forEach((group) => { Object.values(this.state.collectionGroups).forEach((group: ContactType) => {
if (group.members.includes(this.state.uid)) { if (group.members.includes(this.state.uid)) {
this.state.newGroups.push(group); this.state.newGroups.push(group.fn);
this.state.originalGroups.push(group); this.state.originalGroups.push(group.fn);
} }
}); });
this.onSubmit = this.onSubmit.bind(this); this.onSubmit = this.onSubmit.bind(this);
this.addMetadata = this.addMetadata.bind(this); this.addMetadata = this.addMetadata.bind(this);
this.getCollectionGroups = this.getCollectionGroups.bind(this);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
this.handleCollectionChange = this.handleCollectionChange.bind(this); this.handleCollectionChange = this.handleCollectionChange.bind(this);
@ -298,15 +295,25 @@ class ContactEdit extends React.PureComponent<PropsType> {
}); });
} }
public handleChange(name: string, value: string | (string | ContactType)[]) { public handleChange(name: string, value: string | string[]) {
this.setState({ this.setState({
[name]: value, [name]: value,
}); });
} }
public getCollectionGroups(collectionUid: string) {
const groups = {};
this.props.allGroups.forEach((group) => {
if (collectionUid === group.collectionUid) {
groups[group.fn] = group;
}
});
return groups;
}
public reloadGroupSuggestions(collectionUid: string) { public reloadGroupSuggestions(collectionUid: string) {
this.setState({ this.setState({
collectionGroups: this.props.groups.filter((group) => collectionUid === group.collectionUid), collectionGroups: this.getCollectionGroups(collectionUid),
newGroups: [], newGroups: [],
}); });
} }
@ -324,7 +331,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
this.handleChange(name, value); this.handleChange(name, value);
} }
public addMetadata(item: ContactType, uid: string, isGroup?: boolean) { public addMetadata(item: ContactType, uid: string, isGroup: boolean) {
const comp = item.comp; const comp = item.comp;
comp.updatePropertyWithValue("prodid", "-//iCal.js EteSync Web"); comp.updatePropertyWithValue("prodid", "-//iCal.js EteSync Web");
comp.updatePropertyWithValue("version", "4.0"); comp.updatePropertyWithValue("version", "4.0");
@ -345,33 +352,34 @@ class ContactEdit extends React.PureComponent<PropsType> {
; ;
const comp = contact.comp; const comp = contact.comp;
this.addMetadata(contact, this.state.uid, this.props.newGroup); this.addMetadata(contact, this.state.uid, false);
// Add new groups // Add new groups
this.state.newGroups.forEach((group) => { this.state.newGroups.forEach((group) => {
if (typeof(group) === "string") { if (!this.state.collectionGroups[group]) {
const newGroup = new ContactType(new ICAL.Component(["vcard", [], []])); const newGroup = new ContactType(new ICAL.Component(["vcard", [], []]));
this.addMetadata(newGroup, uuid.v4(), true); this.addMetadata(newGroup, uuid.v4(), true);
newGroup.comp.updatePropertyWithValue("fn", group); newGroup.comp.updatePropertyWithValue("fn", group.trim());
newGroup.comp.updatePropertyWithValue("member", `urn:uuid:${this.state.uid}`); newGroup.comp.updatePropertyWithValue("member", `urn:uuid:${this.state.uid}`);
this.props.onSave(newGroup, this.state.collectionUid, undefined); this.props.onSave(newGroup, this.state.collectionUid, undefined);
} else if (!this.state.originalGroups.includes(group)) { } else if (!this.state.originalGroups[group]) {
const updatedGroup = group.clone(); const oldGroup = this.state.collectionGroups[group];
updatedGroup.comp.updatePropertyWithValue("member", `urn:uuid:${this.state.uid}`); const updatedGroup = oldGroup.clone();
this.props.onSave(updatedGroup, this.state.collectionUid, group); updatedGroup.comp.addPropertyWithValue("member", `urn:uuid:${this.state.uid}`);
this.props.onSave(updatedGroup, this.state.collectionUid, oldGroup);
} }
}); });
// Remove deleted groups // Remove deleted groups
this.state.originalGroups.filter((x) => !this.state.newGroups.includes(x)).forEach((deletedGroup) => { this.state.originalGroups.filter((x) => !this.state.newGroups.includes(x)).forEach((removed) => {
const deletedGroup = this.state.collectionGroups[removed];
const updatedGroup = deletedGroup.clone(); const updatedGroup = deletedGroup.clone();
const members = updatedGroup.members.filter((uid) => uid !== this.state.uid); const members = updatedGroup.members.filter((uid: string) => uid !== this.state.uid);
updatedGroup.comp.removeAllProperties("member"); updatedGroup.comp.removeAllProperties("member");
members.forEach((m) => updatedGroup.comp.updatePropertyWithValue("member", `urn:uuid:${m}`)); members.forEach((m: string) => updatedGroup.comp.addPropertyWithValue("member", `urn:uuid:${m}`));
this.props.onSave(updatedGroup, this.state.collectionUid, deletedGroup); this.props.onSave(updatedGroup, this.state.collectionUid, deletedGroup);
}); });
const lastName = this.state.lastName.trim(); const lastName = this.state.lastName.trim();
const firstName = this.state.firstName.trim(); const firstName = this.state.firstName.trim();
const middleName = this.state.middleName.trim(); const middleName = this.state.middleName.trim();
@ -411,15 +419,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
}); });
} }
function setProperty(name: string, value: string) {
comp.removeAllProperties(name);
if (value !== "") {
comp.updatePropertyWithValue(name, value);
}
}
if (!this.props.newGroup && !this.state.group) {
setProperties("tel", this.state.phone); setProperties("tel", this.state.phone);
setProperties("email", this.state.email); setProperties("email", this.state.email);
setProperties("adr", this.state.address); setProperties("adr", this.state.address);
@ -427,10 +426,16 @@ class ContactEdit extends React.PureComponent<PropsType> {
{ type: x.type, value: x.type + ":" + x.value } { type: x.type, value: x.type + ":" + x.value }
))); )));
function setProperty(name: string, value: string) {
comp.removeAllProperties(name);
if (value !== "") {
comp.updatePropertyWithValue(name, value);
}
}
setProperty("org", this.state.org); setProperty("org", this.state.org);
setProperty("title", this.state.title); setProperty("title", this.state.title);
setProperty("note", this.state.note); setProperty("note", this.state.note);
}
this.props.onSave(contact, this.state.collectionUid, this.props.item) this.props.onSave(contact, this.state.collectionUid, this.props.item)
.then(() => { .then(() => {
@ -462,7 +467,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
return ( return (
<React.Fragment> <React.Fragment>
<h2> <h2>
{this.props.item ? this.state.group ? "Edit Group" : "Edit Contact" : this.props.newGroup ? "New Group" : "New Contact"} {this.props.item ? "Edit Contact" : "New Contact"}
</h2> </h2>
<form style={styles.form} onSubmit={this.onSubmit}> <form style={styles.form} onSubmit={this.onSubmit}>
<FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}> <FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}>
@ -480,16 +485,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
</Select> </Select>
</FormControl> </FormControl>
{this.props.newGroup || this.state.group ?
<TextField
name="firstName"
placeholder="Name"
style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.firstName}
onChange={this.handleInputChange}
/>
:
<>
<TextField <TextField
name="namePrefix" name="namePrefix"
placeholder="Prefix" placeholder="Prefix"
@ -651,9 +646,9 @@ class ContactEdit extends React.PureComponent<PropsType> {
style={styles.fullWidth} style={styles.fullWidth}
freeSolo freeSolo
multiple multiple
autoHighlight clearOnBlur
options={this.state.collectionGroups} selectOnFocus
getOptionLabel={(option: ContactType) => option.fn ?? option} options={Object.keys(this.state.collectionGroups)}
value={this.state.newGroups} value={this.state.newGroups}
onChange={(_e, value) => this.handleChange("newGroups", value)} onChange={(_e, value) => this.handleChange("newGroups", value)}
renderInput={(params) => ( renderInput={(params) => (
@ -665,8 +660,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
/> />
)} )}
/> />
</>
}
<div style={styles.submit}> <div style={styles.submit}>
<Button <Button
@ -707,7 +700,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
onOk={() => this.props.onDelete(this.props.item!, this.props.initialCollection!)} onOk={() => this.props.onDelete(this.props.item!, this.props.initialCollection!)}
onCancel={() => this.setState({ showDeleteDialog: false })} onCancel={() => this.setState({ showDeleteDialog: false })}
> >
{`Are you sure you would like to delete this ${this.state.group ? "group" : "contact"}?`} Are you sure you would like to delete this contact?
</ConfirmationDialog> </ConfirmationDialog>
</React.Fragment> </React.Fragment>
); );

@ -0,0 +1,288 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from "react";
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 ConfirmationDialog from "../widgets/ConfirmationDialog";
import { CachedCollection } from "../Pim/helpers";
import * as uuid from "uuid";
import * as ICAL from "ical.js";
import { ContactType } from "../pim-types";
import { History } from "history";
class ValueType {
public type: string;
public value: string;
constructor(type?: string, value?: string) {
this.type = type ? type : "home";
this.value = value ? value : "";
}
}
interface PropsType {
collections: CachedCollection[];
initialCollection?: string;
item?: ContactType;
onSave: (contact: ContactType, collectionUid: string, originalContact?: ContactType) => Promise<void>;
onDelete: (contact: ContactType, collectionUid: string) => void;
onCancel: () => void;
history: History<any>;
allGroups: ContactType[];
}
class GroupEdit extends React.PureComponent<PropsType> {
public state: {
uid: string;
fn: string;
collectionUid: string;
showDeleteDialog: boolean;
collectionGroups: {};
showError: boolean;
};
constructor(props: PropsType) {
super(props);
this.state = {
uid: "",
fn: "",
collectionUid: "",
showDeleteDialog: false,
collectionGroups: {},
showError: false,
};
if (this.props.item !== undefined) {
const group = this.props.item;
this.state.uid = group.uid;
this.state.fn = group.fn;
} else {
this.state.uid = uuid.v4();
}
if (props.initialCollection) {
this.state.collectionUid = props.initialCollection;
} else if (props.collections[0]) {
this.state.collectionUid = props.collections[0].collection.uid;
}
this.state.collectionGroups = this.getCollectionGroups(this.state.collectionUid);
this.onSubmit = this.onSubmit.bind(this);
this.getCollectionGroups = this.getCollectionGroups.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleCollectionChange = this.handleCollectionChange.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);
this.onDeleteRequest = this.onDeleteRequest.bind(this);
}
public addValueType(name: string, _type?: string) {
const type = _type ? _type : "home";
this.setState((prevState) => {
const newArray = prevState[name].slice(0);
newArray.push(new ValueType(type));
return {
...prevState,
[name]: newArray,
};
});
}
public removeValueType(name: string, idx: number) {
this.setState((prevState) => {
const newArray = prevState[name].slice(0);
newArray.splice(idx, 1);
return {
...prevState,
[name]: newArray,
};
});
}
public handleValueTypeChange(name: string, idx: number, value: ValueType) {
this.setState((prevState) => {
const newArray = prevState[name].slice(0);
newArray[idx] = value;
return {
...prevState,
[name]: newArray,
};
});
}
public handleChange(name: string, value: string) {
this.setState({
[name]: value,
});
}
public getCollectionGroups(collectionUid: string) {
const groups = {};
this.props.allGroups.forEach((group) => {
if (collectionUid === group.collectionUid) {
groups[group.fn] = null;
}
});
return groups;
}
public handleCollectionChange(contact: any) {
const name = contact.target.name;
const collectionUid: string = contact.target.value;
this.handleChange(name, collectionUid);
this.setState({ "collectionGroups": this.getCollectionGroups(collectionUid) });
}
public handleInputChange(contact: any) {
const name = contact.target.name;
const value = contact.target.value;
this.handleChange(name, value);
}
public onSubmit(e: React.FormEvent<any>) {
e.preventDefault();
const nameUsed = this.state.fn in this.state.collectionGroups;
if ((this.props.item && this.state.fn !== this.props.item.fn && nameUsed) || (!this.props.item && nameUsed)) {
this.setState({ showError: true });
return;
}
const group = (this.props.item) ?
this.props.item.clone()
:
new ContactType(new ICAL.Component(["vcard", [], []]))
;
const comp = group.comp;
comp.updatePropertyWithValue("prodid", "-//iCal.js EteSync Web");
comp.updatePropertyWithValue("version", "4.0");
comp.updatePropertyWithValue("uid", this.state.uid);
comp.updatePropertyWithValue("rev", ICAL.Time.now());
comp.updatePropertyWithValue("kind", "group");
comp.updatePropertyWithValue("fn", this.state.fn.trim());
this.props.onSave(group, this.state.collectionUid, this.props.item)
.then(() => {
this.props.history.goBack();
});
}
public onDeleteRequest() {
this.setState({
showDeleteDialog: true,
});
}
public render() {
const styles = {
form: {
},
fullWidth: {
width: "100%",
boxSizing: "border-box" as any,
},
submit: {
marginTop: 40,
marginBottom: 20,
textAlign: "right" as any,
},
};
return (
<React.Fragment>
<h2>
{this.props.item ? "Edit Group" : "New Group"}
</h2>
<form style={styles.form} onSubmit={this.onSubmit}>
<FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}>
<InputLabel>
Saving to
</InputLabel>
<Select
name="collectionUid"
value={this.state.collectionUid}
onChange={this.handleCollectionChange}
>
{this.props.collections.map((x) => (
<MenuItem key={x.collection.uid} value={x.collection.uid}>{x.metadata.name}</MenuItem>
))}
</Select>
</FormControl>
<TextField
name="fn"
placeholder="Name"
error={this.state.showError}
helperText="Group names must be unique"
style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.fn}
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 }}
disabled={this.state.fn.length === 0}
>
<IconSave style={{ marginRight: 8 }} />
Save
</Button>
</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 group?
</ConfirmationDialog>
</React.Fragment>
);
}
}
export default GroupEdit;

@ -18,6 +18,7 @@ import SearchableAddressBook from "./SearchableAddressBook";
import Contact from "./Contact"; import Contact from "./Contact";
import LoadingIndicator from "../widgets/LoadingIndicator"; import LoadingIndicator from "../widgets/LoadingIndicator";
import ContactEdit from "./ContactEdit"; import ContactEdit from "./ContactEdit";
import GroupEdit from "./GroupEdit";
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound"; import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers"; import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers";
@ -79,7 +80,7 @@ export default function ContactsMain() {
} }
} }
const groups = flatEntries.filter((x) => x.group); const allGroups = flatEntries.filter((x) => x.group);
const styles = { const styles = {
button: { button: {
@ -115,14 +116,13 @@ export default function ContactsMain() {
path={routeResolver.getRoute("pim.contacts.new.group")} path={routeResolver.getRoute("pim.contacts.new.group")}
exact exact
> >
<ContactEdit <GroupEdit
collections={cachedCollections} collections={cachedCollections}
onSave={onItemSave} onSave={onItemSave}
onDelete={onItemDelete} onDelete={onItemDelete}
onCancel={onCancel} onCancel={onCancel}
history={history} history={history}
newGroup allGroups={allGroups}
groups={groups}
/> />
</Route> </Route>
<Route <Route
@ -135,7 +135,7 @@ export default function ContactsMain() {
onDelete={onItemDelete} onDelete={onItemDelete}
onCancel={onCancel} onCancel={onCancel}
history={history} history={history}
groups={groups} allGroups={allGroups}
/> />
</Route> </Route>
<Route <Route
@ -164,13 +164,27 @@ export default function ContactsMain() {
const collection = collections!.find((x) => x.uid === colUid)!; const collection = collections!.find((x) => x.uid === colUid)!;
const readOnly = collection.accessLevel === Etebase.CollectionAccessLevel.ReadOnly; const readOnly = collection.accessLevel === Etebase.CollectionAccessLevel.ReadOnly;
const path = `pim.contacts._id.edit.${item.group ? "group" : "contact"}`;
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute("pim.contacts._id.edit")} path={routeResolver.getRoute(path)}
exact exact
> >
{item.group ?
<GroupEdit
key={itemUid}
initialCollection={item.collectionUid}
item={item}
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
allGroups={allGroups}
/>
:
<ContactEdit <ContactEdit
key={itemUid} key={itemUid}
initialCollection={item.collectionUid} initialCollection={item.collectionUid}
@ -180,8 +194,9 @@ export default function ContactsMain() {
onDelete={onItemDelete} onDelete={onItemDelete}
onCancel={onCancel} onCancel={onCancel}
history={history} history={history}
groups={groups} allGroups={allGroups}
/> />
}
</Route> </Route>
<Route <Route
path={routeResolver.getRoute("pim.contacts._id")} path={routeResolver.getRoute("pim.contacts._id")}
@ -205,7 +220,7 @@ export default function ContactsMain() {
disabled={readOnly} disabled={readOnly}
style={{ ...styles.button, marginLeft: 15 }} style={{ ...styles.button, marginLeft: 15 }}
onClick={() => onClick={() =>
history.push(routeResolver.getRoute("pim.contacts._id.edit", { itemUid: getItemNavigationUid(item) })) history.push(routeResolver.getRoute(path, { itemUid: getItemNavigationUid(item) }))
} }
> >
<IconEdit style={styles.leftIcon} /> <IconEdit style={styles.leftIcon} />

Loading…
Cancel
Save