322 lines
14 KiB
JavaScript
322 lines
14 KiB
JavaScript
import * as React from "react";
|
||
import Icon from "../elements/icon";
|
||
import Alert from "../elements/alert";
|
||
import {Link} from "react-router-dom";
|
||
import "../include/select2.min.css";
|
||
|
||
export default class EditUser extends React.Component {
|
||
|
||
constructor(props) {
|
||
super(props);
|
||
this.parent = {
|
||
api: props.api,
|
||
showDialog: props.showDialog,
|
||
};
|
||
|
||
this.state = {
|
||
user: {},
|
||
alerts: [],
|
||
fetchError: null,
|
||
loaded: false,
|
||
isSaving: false,
|
||
isDeleting: false,
|
||
groups: { },
|
||
searchString: "",
|
||
searchActive: false
|
||
};
|
||
|
||
this.searchBox = React.createRef();
|
||
}
|
||
|
||
removeAlert(i) {
|
||
if (i >= 0 && i < this.state.alerts.length) {
|
||
let alerts = this.state.alerts.slice();
|
||
alerts.splice(i, 1);
|
||
this.setState({...this.state, alerts: alerts});
|
||
}
|
||
}
|
||
|
||
componentDidMount() {
|
||
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
|
||
if (res.success) {
|
||
this.setState({ ...this.state, user: {
|
||
name: res.user.name,
|
||
email: res.user.email || "",
|
||
groups: res.user.groups,
|
||
password: "",
|
||
confirmed: res.user.confirmed
|
||
}
|
||
});
|
||
this.parent.api.fetchGroups(1, 50).then((res) => {
|
||
if (res.success) {
|
||
this.setState({ ...this.state, groups: res.groups, loaded: true });
|
||
} else {
|
||
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
||
}
|
||
});
|
||
} else {
|
||
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
||
}
|
||
});
|
||
}
|
||
|
||
onChangeInput(event) {
|
||
const target = event.target;
|
||
let value = target.value;
|
||
const name = target.name;
|
||
|
||
if (target.type === "checkbox") {
|
||
value = !!target.checked;
|
||
}
|
||
|
||
if (name === "search") {
|
||
this.setState({...this.state, searchString: value});
|
||
} else {
|
||
this.setState({ ...this.state, user: { ...this.state.user, [name]: value } });
|
||
}
|
||
}
|
||
|
||
onToggleSearch(e) {
|
||
e.stopPropagation();
|
||
this.setState({ ...this.state, searchActive: !this.state.searchActive });
|
||
this.searchBox.current.focus();
|
||
}
|
||
|
||
onSubmitForm(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
const id = this.props.match.params["userId"];
|
||
const username = this.state.user["name"];
|
||
const email = this.state.user["email"];
|
||
let password = this.state.user["password"].length > 0 ? this.state.user["password"] : null;
|
||
let groups = Object.keys(this.state.user.groups);
|
||
let confirmed = this.state.user["confirmed"];
|
||
|
||
this.setState({ ...this.state, isSaving: true});
|
||
this.parent.api.editUser(id, username, email, password, groups, confirmed).then((res) => {
|
||
let alerts = this.state.alerts.slice();
|
||
|
||
if (res.success) {
|
||
alerts.push({ title: "Success", message: "User was successfully updated.", type: "success" });
|
||
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
|
||
} else {
|
||
alerts.push({ title: "Error updating user", message: res.msg, type: "danger" });
|
||
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
|
||
}
|
||
});
|
||
}
|
||
|
||
onDeleteUser(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
const id = this.props.match.params["userId"];
|
||
|
||
this.parent.showDialog("Are you sure you want to delete this user permanently?", "Delete User?", ["Yes", "No"], (btn) => {
|
||
if (btn === "Yes") {
|
||
this.parent.api.deleteUser(id).then((res) => {
|
||
if (res.success) {
|
||
this.props.history.push("/admin/users");
|
||
} else {
|
||
let alerts = this.state.alerts.slice();
|
||
alerts.push({ title: "Error deleting user", message: res.msg, type: "danger" });
|
||
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
|
||
}
|
||
})
|
||
}
|
||
});
|
||
}
|
||
|
||
onRemoveGroup(event, groupId) {
|
||
event.stopPropagation();
|
||
if (this.state.user.groups.hasOwnProperty(groupId)) {
|
||
let groups = { ...this.state.user.groups };
|
||
delete groups[groupId];
|
||
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }});
|
||
}
|
||
}
|
||
|
||
onAddGroup(event, groupId) {
|
||
event.stopPropagation();
|
||
if (!this.state.user.groups.hasOwnProperty(groupId)) {
|
||
let groups = { ...this.state.user.groups, [groupId]: { ...this.state.groups[groupId] } };
|
||
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }, searchActive: false, searchString: "" });
|
||
}
|
||
}
|
||
|
||
render() {
|
||
if (!this.state.loaded) {
|
||
return <h2 className={"text-center"}>
|
||
Loading…<br/>
|
||
<Icon icon={"spinner"} className={"mt-3 text-muted fa-2x"}/>
|
||
</h2>
|
||
}
|
||
|
||
let alerts = [];
|
||
let form = null;
|
||
if(this.state.fetchError) {
|
||
alerts.push(
|
||
<Alert key={"error-fetch"} title={"Error fetching data"} type={"danger"} message={
|
||
<div>{this.state.fetchError}<br/>You can meanwhile return to the
|
||
<Link to={"/admin/users"}>user overview</Link>
|
||
</div>
|
||
}/>
|
||
)
|
||
} else {
|
||
|
||
for (let i = 0; i < this.state.alerts.length; i++) {
|
||
alerts.push(<Alert key={"error-" + i} onClose={() => this.removeAlert(i)} {...this.state.alerts[i]}/>)
|
||
}
|
||
|
||
let possibleOptions = [];
|
||
let renderedOptions = [];
|
||
for (let groupId in this.state.groups) {
|
||
if (this.state.groups.hasOwnProperty(groupId)) {
|
||
let groupName = this.state.groups[groupId].name;
|
||
let groupColor = this.state.groups[groupId].color;
|
||
if (this.state.user.groups.hasOwnProperty(groupId)) {
|
||
renderedOptions.push(
|
||
<li className={"select2-selection__choice"} key={"group-" + groupId} title={groupName} style={{backgroundColor: groupColor}}>
|
||
<span className="select2-selection__choice__remove" role="presentation"
|
||
onClick={(e) => this.onRemoveGroup(e, groupId)}>
|
||
×
|
||
</span>
|
||
{groupName}
|
||
</li>
|
||
);
|
||
} else {
|
||
if (this.state.searchString.length === 0 || groupName.toLowerCase().includes(this.state.searchString.toLowerCase())) {
|
||
possibleOptions.push(
|
||
<li className={"select2-results__option"} role={"option"} key={"group-" + groupId} aria-selected={false}
|
||
onClick={(e) => this.onAddGroup(e, groupId)}>
|
||
{groupName}
|
||
</li>
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
let searchWidth = "100%";
|
||
let placeholder = "Select Groups";
|
||
let searchVisible = (this.state.searchString.length > 0 || this.state.searchActive) ? "block" : "none";
|
||
if (renderedOptions.length > 0) {
|
||
searchWidth = (0.75 + this.state.searchString.length * 0.75) + "em";
|
||
placeholder = "";
|
||
}
|
||
|
||
if (this.state.searchString.length > 0 && possibleOptions.length === 0) {
|
||
possibleOptions.push(
|
||
<li className={"select2-results__option"} role={"option"} key={"group-notfound"} aria-selected={true}>
|
||
Group not found
|
||
</li>
|
||
);
|
||
}
|
||
|
||
form = <form role={"form"} onSubmit={(e) => e.preventDefault()}>
|
||
<div className={"form-group"}>
|
||
<label htmlFor={"username"}>Username</label>
|
||
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
|
||
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}
|
||
onChange={this.onChangeInput.bind(this)}/>
|
||
</div>
|
||
<div className={"form-group"}>
|
||
<label htmlFor={"email"}>E-Mail</label>
|
||
<input type={"email"} className={"form-control"} placeholder={"E-Mail address"}
|
||
id={"email"} name={"email"} maxLength={64} value={this.state.user.email}
|
||
onChange={this.onChangeInput.bind(this)}/>
|
||
</div>
|
||
|
||
<div className={"form-group"}>
|
||
<label htmlFor={"password"}>Password</label>
|
||
<input type={"password"} className={"form-control"} placeholder={"(unchanged)"}
|
||
id={"password"} name={"password"} value={this.state.user.password}
|
||
onChange={this.onChangeInput.bind(this)}/>
|
||
</div>
|
||
|
||
<div className={"form-group position-relative"}>
|
||
<label>Groups</label>
|
||
<span className={"select2 select2-container select2-container--default select2-container--below"}
|
||
dir={"ltr"} style={{width: "100%"}} >
|
||
<span className="selection">
|
||
<span className={"select2-selection select2-selection--multiple"} role={"combobox"} aria-haspopup={"true"}
|
||
aria-expanded={false} aria-disabled={false} onClick={this.onToggleSearch.bind(this)}>
|
||
<ul className={"select2-selection__rendered"}>
|
||
{renderedOptions}
|
||
<li className={"select2-search select2-search--inline"}>
|
||
<input className={"select2-search__field"} type={"search"} tabIndex={0}
|
||
autoComplete={"off"} autoCorrect={"off"} autoCapitalize={"none"} spellCheck={false}
|
||
role={"searchbox"} aria-autocomplete={"list"} placeholder={placeholder}
|
||
name={"search"} style={{width: searchWidth}} value={this.state.searchString}
|
||
onChange={this.onChangeInput.bind(this)} ref={this.searchBox} />
|
||
</li>
|
||
</ul>
|
||
</span>
|
||
</span>
|
||
<span className="dropdown-wrapper" aria-hidden="true"/>
|
||
</span>
|
||
<span className={"select2-container select2-container--default select2-container--open"}
|
||
style={{position: "absolute", bottom: 0, left: 0, width: "100%", display: searchVisible}}>
|
||
<span className={"select2-dropdown select2-dropdown--below"} dir={"ltr"}>
|
||
<span className={"select2-results"}>
|
||
<ul className={"select2-results__options"} role={"listbox"}
|
||
aria-multiselectable={true} aria-expanded={true} aria-hidden={false}>
|
||
{possibleOptions}
|
||
</ul>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div className={"form-check"}>
|
||
<input type={"checkbox"} className={"form-check-input"}
|
||
onChange={this.onChangeInput.bind(this)}
|
||
id={"confirmed"} name={"confirmed"} checked={this.state.user.confirmed}/>
|
||
<label className={"form-check-label"} htmlFor={"confirmed"}>
|
||
Confirmed
|
||
</label>
|
||
</div>
|
||
|
||
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
|
||
<Icon icon={"arrow-left"}/>
|
||
Back
|
||
</Link>
|
||
{ this.state.isSaving
|
||
? <button type={"submit"} className={"btn btn-primary mt-2 mr-2"} disabled>Saving… <Icon icon={"circle-notch"} /></button>
|
||
: <button type={"submit"} className={"btn btn-primary mt-2 mr-2"} onClick={this.onSubmitForm.bind(this)}>Save</button>
|
||
}
|
||
{ this.state.isDeleting
|
||
? <button type={"submit"} className={"btn btn-danger mt-2"} disabled>Deleting… <Icon icon={"circle-notch"} /></button>
|
||
: <button type={"submit"} className={"btn btn-danger mt-2"} onClick={this.onDeleteUser.bind(this)}>Delete</button>
|
||
}
|
||
</form>
|
||
}
|
||
|
||
return <>
|
||
<div className="content-header">
|
||
<div className="container-fluid">
|
||
<div className="row mb-2">
|
||
<div className="col-sm-6">
|
||
<h1 className="m-0 text-dark">Edit User</h1>
|
||
</div>
|
||
<div className="col-sm-6">
|
||
<ol className="breadcrumb float-sm-right">
|
||
<li className="breadcrumb-item"><Link to={"/admin/dashboard"}>Home</Link></li>
|
||
<li className="breadcrumb-item"><Link to={"/admin/users"}>Users</Link></li>
|
||
<li className="breadcrumb-item active">Add User</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className={"content"}>
|
||
<div className={"row"}>
|
||
<div className={"col-lg-6 pl-5 pr-5"}>
|
||
{alerts}
|
||
{form}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
}
|
||
} |