Commit 80a00639 authored by Juorder Gonzalez's avatar Juorder Gonzalez

Merge pull request #22 from ZBoxApp/domains

Domain deatils patialy done, modal example and a few utils
parents b1ad0f75 f553a80e
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as Utils from '../utils/utils.jsx';
import StatusLabel from '../components/status_label.jsx';
import {Modal} from 'react-bootstrap';
import React from 'react';
export default class AddAdminModal extends React.Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.handleAddAdmin = this.handleAddAdmin.bind(this);
this.state = {
users: null
};
}
handleSearch(e) {
e.preventDefault();
const query = this.refs.searchUser.value.trim();
GlobalActions.emitStartLoading();
if (query) {
this.setState({users: []});
} else {
this.setState({users: null});
}
}
handleAddAdmin(e, user) {
e.preventDefault();
console.log(user); //eslint-disable-line no-console
}
shouldComponentUpdate(nextProps, nextState) {
if (!this.props.show && !nextProps.show) {
return false;
}
if (!Utils.areObjectsEqual(this.props, nextProps)) {
return true;
}
return !Utils.areObjectsEqual(this.state, nextState);
}
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
this.setState({users: null});
} else {
GlobalActions.emitEndLoading();
}
}
render() {
const users = this.state.users;
let results;
if (users) {
if (users.length === 0) {
results = (
<div style={{margin: '20px 0'}}>
<div className='empty-search'>
{'Sin resultados para tu búsqueda'}
</div>
</div>
);
} else {
const rows = users.map((u) => {
const statusClass = 'mailbox-active'; //esto debe ser dependiendo del status que tenga el usuario
return (
<tr
key={`user-${u.id}`}
className='mailbox-row'
>
<td className='mailbox-status'>
<StatusLabel
classes={`label-mailbox ${statusClass}`}
children={u.status}
/>
</td>
<td className='mailbox-name'>
<a
href='#'
onClick={(e) => {
Utils.handleLink(e, `/mailboxes/${u.id}`);
}}
>
{u.email}
</a>
</td>
<td className='mailbox-displayname'>
{u.givenName}
</td>
<td className='text-center'>
<a
className='btn btn-warning btn-xs'
onClick={(e) => this.handleAddAdmin(e, u)}
>
{'Activar Admin'}
</a>
</td>
</tr>
);
});
results = (
<table
cellPadding='1'
cellSpacing='1'
className='table table-condensed table-striped vertical-align index-mailbox-table'
>
<thead>
<tr>
<th></th>
<th>{'Email'}</th>
<th className='text-left'>{'Nombre'}</th>
<th className='text-center'>{' Administrador '}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
);
}
}
return (
<Modal
show={this.props.show}
onHide={this.props.onHide}
>
<div className='color-line'></div>
<Modal.Header closeButton={true}>
<Modal.Title>
{'Nuevo administador de dominio'}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
{'El administrador debe tener una casilla de correo creada en la plataforma.'}
<br/>
{'Si no está creada, '}
<a
href='#'
onClick={(e) => Utils.handleLink(e, `/domains/${this.props.domain.id}/mailboxes/new`)}
>
{'la puedes crear ahora.'}
</a>
</p>
<div className='panel-header-search'>
<form onSubmit={this.handleSearch}>
<div className='input-group'>
<input
type='text'
ref='searchUser'
className='form-control'
placeholder='Buscar Por Nombre'
/>
<span className='input-group-btn'>
<button
className='btn btn-default'
type='submit'
>
<span className='fa fa-search'></span>
</button>
</span>
</div>
</form>
</div>
{results}
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.props.onHide}
>
{'Cancelar'}
</button>
</Modal.Footer>
</Modal>
);
}
}
AddAdminModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
domain: React.PropTypes.object.isRequired
};
......@@ -3,7 +3,6 @@
import $ from 'jquery';
import React from 'react';
import {browserHistory} from 'react-router';
import MessageBar from '../message_bar.jsx';
import PageInfo from '../page_info.jsx';
......@@ -11,26 +10,29 @@ import PanelTab from '../panel_tab.jsx';
import Panel from '../panel.jsx';
import StatusLabel from '../status_label.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import AddAdminModal from '../add_admin_modal.jsx';
import DomainStore from '../../stores/domain_store.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
export default class DomainDetails extends React.Component {
constructor(props) {
super(props);
this.handleLink = this.handleLink.bind(this);
this.getDomain = this.getDomain.bind(this);
this.handleDeleteAdmin = this.handleDeleteAdmin.bind(this);
this.state = {};
}
handleLink(e, path) {
handleDeleteAdmin(e, admin) {
e.preventDefault();
if (`/${this.props.location.pathname}` !== path) {
GlobalActions.emitStartLoading();
browserHistory.push(path);
}
console.log(admin); //eslint-disable-line no-console
//here we need to delete the admin and show a message of success or error
}
getDomain() {
const domain = DomainStore.getCurrent();
......@@ -96,7 +98,7 @@ export default class DomainDetails extends React.Component {
label: 'Editar',
props: {
className: 'btn btn-default btn-xs',
onClick: (e) => this.handleLink(e, `/domains/${this.props.params.id}/edit`)
onClick: (e) => Utils.handleLink(e, `/domains/${this.props.params.id}/edit`, this.props.location)
}
}];
......@@ -115,7 +117,7 @@ export default class DomainDetails extends React.Component {
<p>
<a
className='account-name'
onClick={(e) => this.handleLink(e, `/accounts/${domain.id_empresa}`)}
onClick={(e) => Utils.handleLink(e, `/accounts/${domain.id_empresa}`, this.props.location)}
>
{'Nombre de la Empresa'}
</a>
......@@ -154,14 +156,14 @@ export default class DomainDetails extends React.Component {
label: 'Ver casillas',
props: {
className: 'btn btn-default btn-xs',
onClick: (e) => this.handleLink(e, `/domains/${this.props.params.id}/mailboxes`)
onClick: (e) => Utils.handleLink(e, `/domains/${this.props.params.id}/mailboxes`, this.props.location)
}
},
{
label: 'Nueva Casilla',
props: {
className: 'btn btn-info add-button btn-xs',
onClick: (e) => this.handleLink(e, `/domains/${this.props.params.id}/mailboxes/new`)
onClick: (e) => Utils.handleLink(e, `/domains/${this.props.params.id}/mailboxes/new`, this.props.location)
}
}
];
......@@ -204,11 +206,99 @@ export default class DomainDetails extends React.Component {
</table>
);
const tab1 = (
const admins = []; // this should be the actual admins
const adminRows = admins.map((a) => {
return (
<tr
key={`admin-${a.id}`}
className='user-row'
>
<td className='user-email'>
{a.email}
</td>
<td className='user-name text-center'>
{a.username}
</td>
<td className='user-type text-center'>
{a.type}
</td>
<td className='user-actions text-center'>
<ul className='list-inline list-unstyled'>
<li>
<a
className='btn btn-default btn-xs'
onClick={(e) => Utils.handleLink(e, `/mailboxes/${a.id}/edit`, this.props.location)}
>
{'Editar'}
</a>
</li>
<li>
<a
className='btn btn-danger btn-xs'
onClick={(e) => this.handleDeleteAdmin(e, a)}
>
{'Eliminar'}
</a>
</li>
</ul>
</td>
</tr>
);
});
let adminContent;
if (adminRows.length > 0) {
adminContent = (
<div className='table-responsive'>
<table
id='index-users'
cellPadding='1'
cellSpacing='1'
className='table table-condensed table-striped vertical-align'
>
<thead>
<tr>
<th>{'email'}</th>
<th className='text-center'>{'Nombre'}</th>
<th></th>
<th className='text-center'>{'Acciones'}</th>
</tr>
</thead>
<tbody>
{adminRows}
</tbody>
</table>
<ToggleModalButton
role='button'
className='btn btn-default'
dialogType={AddAdminModal}
dialogProps={{domain}}
>
{'Agregar administrador'}
</ToggleModalButton>
</div>
);
} else {
adminContent = (
<div className='empty-message'>
<h4>
{'No existen Administradores. '}
<ToggleModalButton
role='button'
dialogType={AddAdminModal}
dialogProps={{domain}}
>
{'Agregar administrador'}
</ToggleModalButton>
</h4>
</div>
);
}
const tabAdmin = (
<Panel
title='Información General'
btnsHeader={editDomainButton}
children={infoBody}
hasHeader={false}
children={adminContent}
/>
);
......@@ -224,7 +314,7 @@ export default class DomainDetails extends React.Component {
<PanelTab
tabNames={['Administradores', 'Listas De Distribución']}
tabs={{
administradores: tab1,
administradores: tabAdmin,
listas_de_distribución: tab2
}}
/>
......
import React from 'react';
// Cambiar por algo que reciba el status y el tamaño y se cree el componente de una vez
export default class StatusLabel extends React.Component {
render() {
return (
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
export default class ModalToggleButton extends React.Component {
constructor(props) {
super(props);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
this.state = {
show: false
};
}
show(e) {
if (e) {
e.preventDefault();
}
this.setState({show: true});
}
hide() {
this.setState({show: false});
}
render() {
const {children, dialogType, dialogProps, onClick, ...props} = this.props; // eslint-disable-line no-use-before-define
// allow callers to provide an onClick which will be called before the modal is shown
let clickHandler = this.show;
if (onClick) {
clickHandler = () => {
onClick();
this.show();
};
}
// this assumes that all modals will have a show property and an onHide event
const dialog = React.createElement(dialogType, Object.assign({}, dialogProps, {
show: this.state.show,
onHide: () => {
this.hide();
if (dialogProps.onHide) {
dialogProps.onHide();
}
}
}));
// nesting the dialog in the anchor tag looks like it shouldn't work, but it does due to how react-bootstrap
// renders modals at the top level of the DOM instead of where you specify in the virtual DOM
return (
<a
{...props}
href='#'
onClick={clickHandler}
>
{children}
{dialog}
</a>
);
}
}
ModalToggleButton.propTypes = {
children: React.PropTypes.node.isRequired,
dialogType: React.PropTypes.func.isRequired,
dialogProps: React.PropTypes.object,
onClick: React.PropTypes.func
};
ModalToggleButton.defaultProps = {
dialogProps: {}
};
......@@ -1257,8 +1257,8 @@ fieldset {
}
}
.panel-heading {
.btn {
margin-left: 5px;
.hpanel {
.btn:not(:last-child) {
margin-right: 5px;
}
}
......@@ -32,17 +32,13 @@
.modal-header {
background: $color-bright;
padding: 40px 30px;
padding: 15px;
small {
color: lighten($color-text, 20%);
}
}
.modal-body {
padding: 20px 30px;
}
.modal-footer {
background: $color-bright;
}
......
......@@ -19,3 +19,7 @@
font-weight: bold;
margin-right: 5px;
}
.empty-search {
margin-top: 10px;
}
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import {browserHistory} from 'react-router';
import * as GlobalActions from '../action_creators/global_actions.jsx';
const COOKIE_TIMEOUT = 24 * 60 * 60 * 1000;
export function setCookie(cname, cvalue, exdays) {
......@@ -28,3 +31,131 @@ export function getCookie(cname) {
export function slug(str) {
return str.toLowerCase().replace(/ /g, '_');
}
export function areObjectsEqual(x, y) {
let p;
const leftChain = [];
const rightChain = [];
// Remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}
// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on step when comparing prototypes
if (x === y) {
return true;
}
// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}
if (x instanceof Map && y instanceof Map) {
return areMapsEqual(x, y);
}
// At last checking prototypes as good a we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
}
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}
if (x.constructor !== y.constructor) {
return false;
}
if (x.prototype !== y.prototype) {
return false;
}
// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}
// Quick checking of one object beeing a subset of another.
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
}
for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
switch (typeof (x[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!areObjectsEqual(x[p], y[p])) {
return false;
}
leftChain.pop();
rightChain.pop();
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}
return true;
}
export function areMapsEqual(a, b) {
if (a.size !== b.size) {
return false;
}
for (let [key, value] of a) {
if (!b.has(key)) {
return false;
}
if (!areObjectsEqual(value, b.get(key))) {
return false;
}
}
return true;
}
export function handleLink(e, path, location) {
e.preventDefault();
if (location) {
if (`/${location.pathname}` !== path) {
GlobalActions.emitStartLoading();
browserHistory.push(path);
}
} else {
GlobalActions.emitStartLoading();
browserHistory.push(path);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment