Create GroupEdit

master
Ramzan 4 years ago
parent 2505650708
commit 6ab60f24ef

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

@ -121,12 +121,11 @@ interface PropsType {
collections: CachedCollection[];
initialCollection?: string;
item?: ContactType;
newGroup?: boolean;
onSave: (contact: ContactType, collectionUid: string, originalContact?: ContactType) => Promise<void>;
onDelete: (contact: ContactType, collectionUid: string) => void;
onCancel: () => void;
history: History<any>;
groups: ContactType[];
allGroups: ContactType[];
}
class ContactEdit extends React.PureComponent<PropsType> {
@ -145,13 +144,12 @@ class ContactEdit extends React.PureComponent<PropsType> {
org: string;
note: string;
title: string;
group: boolean;
collectionUid: string;
showDeleteDialog: boolean;
collectionGroups: ContactType[];
newGroups: (string | ContactType)[];
originalGroups: ContactType[];
collectionGroups: {};
newGroups: string[];
originalGroups: string[];
};
constructor(props: PropsType) {
@ -171,11 +169,10 @@ class ContactEdit extends React.PureComponent<PropsType> {
org: "",
note: "",
title: "",
group: false,
collectionUid: "",
showDeleteDialog: false,
collectionGroups: [],
collectionGroups: {},
newGroups: [],
originalGroups: [],
};
@ -183,7 +180,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
if (this.props.item !== undefined) {
const contact = this.props.item;
this.state.group = contact.group;
this.state.uid = contact.uid;
this.state.fn = contact.fn ? contact.fn : "";
if (contact.n) {
@ -244,16 +240,17 @@ class ContactEdit extends React.PureComponent<PropsType> {
this.state.collectionUid = props.collections[0].collection.uid;
}
this.state.collectionGroups = this.props.groups.filter((group) => this.state.collectionUid === group.collectionUid);
this.state.collectionGroups.forEach((group) => {
this.state.collectionGroups = this.getCollectionGroups(this.state.collectionUid);
Object.values(this.state.collectionGroups).forEach((group: ContactType) => {
if (group.members.includes(this.state.uid)) {
this.state.newGroups.push(group);
this.state.originalGroups.push(group);
this.state.newGroups.push(group.fn);
this.state.originalGroups.push(group.fn);
}
});
this.onSubmit = this.onSubmit.bind(this);
this.addMetadata = this.addMetadata.bind(this);
this.getCollectionGroups = this.getCollectionGroups.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInputChange = this.handleInputChange.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({
[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) {
this.setState({
collectionGroups: this.props.groups.filter((group) => collectionUid === group.collectionUid),
collectionGroups: this.getCollectionGroups(collectionUid),
newGroups: [],
});
}
@ -324,7 +331,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
this.handleChange(name, value);
}
public addMetadata(item: ContactType, uid: string, isGroup?: boolean) {
public addMetadata(item: ContactType, uid: string, isGroup: boolean) {
const comp = item.comp;
comp.updatePropertyWithValue("prodid", "-//iCal.js EteSync Web");
comp.updatePropertyWithValue("version", "4.0");
@ -345,33 +352,34 @@ class ContactEdit extends React.PureComponent<PropsType> {
;
const comp = contact.comp;
this.addMetadata(contact, this.state.uid, this.props.newGroup);
this.addMetadata(contact, this.state.uid, false);
// Add new groups
this.state.newGroups.forEach((group) => {
if (typeof(group) === "string") {
if (!this.state.collectionGroups[group]) {
const newGroup = new ContactType(new ICAL.Component(["vcard", [], []]));
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}`);
this.props.onSave(newGroup, this.state.collectionUid, undefined);
} else if (!this.state.originalGroups.includes(group)) {
const updatedGroup = group.clone();
updatedGroup.comp.updatePropertyWithValue("member", `urn:uuid:${this.state.uid}`);
this.props.onSave(updatedGroup, this.state.collectionUid, group);
} else if (!this.state.originalGroups[group]) {
const oldGroup = this.state.collectionGroups[group];
const updatedGroup = oldGroup.clone();
updatedGroup.comp.addPropertyWithValue("member", `urn:uuid:${this.state.uid}`);
this.props.onSave(updatedGroup, this.state.collectionUid, oldGroup);
}
});
// 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 members = updatedGroup.members.filter((uid) => uid !== this.state.uid);
const members = updatedGroup.members.filter((uid: string) => uid !== this.state.uid);
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);
});
const lastName = this.state.lastName.trim();
const firstName = this.state.firstName.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("email", this.state.email);
setProperties("adr", this.state.address);
@ -427,10 +426,16 @@ class ContactEdit extends React.PureComponent<PropsType> {
{ 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("title", this.state.title);
setProperty("note", this.state.note);
}
this.props.onSave(contact, this.state.collectionUid, this.props.item)
.then(() => {
@ -462,7 +467,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
return (
<React.Fragment>
<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>
<form style={styles.form} onSubmit={this.onSubmit}>
<FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}>
@ -480,16 +485,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
</Select>
</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
name="namePrefix"
placeholder="Prefix"
@ -651,9 +646,9 @@ class ContactEdit extends React.PureComponent<PropsType> {
style={styles.fullWidth}
freeSolo
multiple
autoHighlight
options={this.state.collectionGroups}
getOptionLabel={(option: ContactType) => option.fn ?? option}
clearOnBlur
selectOnFocus
options={Object.keys(this.state.collectionGroups)}
value={this.state.newGroups}
onChange={(_e, value) => this.handleChange("newGroups", value)}
renderInput={(params) => (
@ -665,8 +660,6 @@ class ContactEdit extends React.PureComponent<PropsType> {
/>
)}
/>
</>
}
<div style={styles.submit}>
<Button
@ -707,7 +700,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
onOk={() => this.props.onDelete(this.props.item!, this.props.initialCollection!)}
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>
</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 LoadingIndicator from "../widgets/LoadingIndicator";
import ContactEdit from "./ContactEdit";
import GroupEdit from "./GroupEdit";
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
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 = {
button: {
@ -115,14 +116,13 @@ export default function ContactsMain() {
path={routeResolver.getRoute("pim.contacts.new.group")}
exact
>
<ContactEdit
<GroupEdit
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
newGroup
groups={groups}
allGroups={allGroups}
/>
</Route>
<Route
@ -135,7 +135,7 @@ export default function ContactsMain() {
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
groups={groups}
allGroups={allGroups}
/>
</Route>
<Route
@ -164,13 +164,27 @@ export default function ContactsMain() {
const collection = collections!.find((x) => x.uid === colUid)!;
const readOnly = collection.accessLevel === Etebase.CollectionAccessLevel.ReadOnly;
const path = `pim.contacts._id.edit.${item.group ? "group" : "contact"}`;
return (
<Switch>
<Route
path={routeResolver.getRoute("pim.contacts._id.edit")}
path={routeResolver.getRoute(path)}
exact
>
{item.group ?
<GroupEdit
key={itemUid}
initialCollection={item.collectionUid}
item={item}
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
allGroups={allGroups}
/>
:
<ContactEdit
key={itemUid}
initialCollection={item.collectionUid}
@ -180,8 +194,9 @@ export default function ContactsMain() {
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
groups={groups}
allGroups={allGroups}
/>
}
</Route>
<Route
path={routeResolver.getRoute("pim.contacts._id")}
@ -205,7 +220,7 @@ export default function ContactsMain() {
disabled={readOnly}
style={{ ...styles.button, marginLeft: 15 }}
onClick={() =>
history.push(routeResolver.getRoute("pim.contacts._id.edit", { itemUid: getItemNavigationUid(item) }))
history.push(routeResolver.getRoute(path, { itemUid: getItemNavigationUid(item) }))
}
>
<IconEdit style={styles.leftIcon} />

Loading…
Cancel
Save