WiP on segments
This commit is contained in:
parent
6cc34136f5
commit
f3ff89c536
21 changed files with 945 additions and 352 deletions
|
|
@ -19,6 +19,14 @@ import 'brace/mode/json';
|
|||
import 'brace/mode/handlebars';
|
||||
import 'brace/theme/github';
|
||||
|
||||
import DayPicker from 'react-day-picker';
|
||||
import 'react-day-picker/lib/style.css';
|
||||
import { parseDate, parseBirthday, formatDate, formatBirthday, DateFormat, birthdayYear, getDateFormatString, getBirthdayFormatString } from '../../../shared/date';
|
||||
|
||||
import styles from "./styles.scss";
|
||||
import moment from "moment";
|
||||
|
||||
|
||||
const FormState = {
|
||||
Loading: 0,
|
||||
LoadingWithNotice: 1,
|
||||
|
|
@ -79,7 +87,7 @@ class Form extends Component {
|
|||
|
||||
if (!owner.isFormReady()) {
|
||||
if (owner.isFormWithLoadingNotice()) {
|
||||
return <p className={`alert alert-info mt-form-status`} role="alert">{t('Loading ...')}</p>
|
||||
return <p className={`alert alert-info ${styles.formStatus}`} role="alert">{t('Loading ...')}</p>
|
||||
} else {
|
||||
return <div></div>;
|
||||
}
|
||||
|
|
@ -91,7 +99,7 @@ class Form extends Component {
|
|||
</fieldset>
|
||||
{statusMessageText &&
|
||||
<AlignedRow htmlId="form-status-message">
|
||||
<p className={`alert alert-${statusMessageSeverity} mt-form-status`} role="alert">{statusMessageText}</p>
|
||||
<p className={`alert alert-${statusMessageSeverity} ${styles.formStatus}`} role="alert">{statusMessageText}</p>
|
||||
</AlignedRow>
|
||||
}
|
||||
</form>
|
||||
|
|
@ -295,6 +303,111 @@ class TextArea extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class DatePicker extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
opened: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
format: PropTypes.string,
|
||||
birthday: PropTypes.bool,
|
||||
dateFormat: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
dateFormat: DateFormat.INTL
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
formStateOwner: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
toggleDayPicker() {
|
||||
this.setState({
|
||||
opened: !this.state.opened
|
||||
});
|
||||
}
|
||||
|
||||
daySelected(date) {
|
||||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const props = this.props;
|
||||
owner.updateFormValue(id, props.birthday ? formatBirthday(props.dateFormat, date) : formatDate(props.dateFormat, date));
|
||||
|
||||
this.setState({
|
||||
opened: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const htmlId = 'form_' + id;
|
||||
|
||||
function BirthdayPickerCaption({ date, localeUtils, onChange }) {
|
||||
const months = localeUtils.getMonths();
|
||||
return (
|
||||
<div className="DayPicker-Caption">
|
||||
{months[date.getMonth()]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let selectedDate, captionElement, fromMonth, toMonth, placeholder;
|
||||
const selectedDateStr = owner.getFormValue(id) || '';
|
||||
if (props.birthday) {
|
||||
selectedDate = parseBirthday(props.dateFormat, selectedDateStr);
|
||||
if (!selectedDate) {
|
||||
selectedDate = moment().set('year', birthdayYear).toDate();
|
||||
}
|
||||
|
||||
captionElement = <BirthdayPickerCaption/>;
|
||||
fromMonth = new Date(birthdayYear, 0, 1);
|
||||
toMonth = new Date(birthdayYear, 11, 31);
|
||||
placeholder = getBirthdayFormatString(props.dateFormat);
|
||||
|
||||
} else {
|
||||
selectedDate = parseDate(props.dateFormat, selectedDateStr);
|
||||
if (!selectedDate) {
|
||||
selectedDate = moment().toDate();
|
||||
}
|
||||
|
||||
placeholder = getDateFormatString(props.dateFormat);
|
||||
}
|
||||
|
||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<div>
|
||||
<div className="input-group">
|
||||
<input type="text" value={selectedDateStr} placeholder={placeholder} id={htmlId} className="form-control" aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
|
||||
<span className="input-group-addon" onClick={::this.toggleDayPicker}><span className="glyphicon glyphicon-th"></span></span>
|
||||
</div>
|
||||
{this.state.opened &&
|
||||
<div className={styles.dayPickerWrapper}>
|
||||
<DayPicker
|
||||
onDayClick={date => this.daySelected(date)}
|
||||
selectedDays={selectedDate}
|
||||
initialMonth={selectedDate}
|
||||
fromMonth={fromMonth}
|
||||
toMonth={toMonth}
|
||||
captionElement={captionElement}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Dropdown extends Component {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
|
|
@ -374,7 +487,7 @@ class ButtonRow extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let className = 'mt-button-row';
|
||||
let className = styles.buttonRow;
|
||||
if (this.props.className) {
|
||||
className += ' ' + this.props.className;
|
||||
}
|
||||
|
|
@ -554,13 +667,13 @@ class TableSelect extends Component {
|
|||
if (props.dropdown) {
|
||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<div>
|
||||
<div className="input-group mt-tableselect-dropdown">
|
||||
<div className={`input-group ${styles.tableSelectDropdown}`}>
|
||||
<input type="text" className="form-control" value={this.state.selectedLabel} readOnly onClick={::this.toggleOpen}/>
|
||||
<span className="input-group-btn">
|
||||
<ActionButton label={t('Select')} className="btn-default" onClickAsync={::this.toggleOpen}/>
|
||||
</span>
|
||||
</div>
|
||||
<div className={'mt-tableselect-table' + (this.state.open ? '' : ' mt-tableselect-table-hidden')}>
|
||||
<div className={styles.tableSelectTable + (this.state.open ? '' : ' ' + styles.tableSelectTableHidden)}>
|
||||
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -834,13 +947,29 @@ function withForm(target) {
|
|||
this.setState(previousState => {
|
||||
const oldValue = previousState.formState.getIn(['data', key, 'value']);
|
||||
|
||||
const onChangeBeforeValidationCallback = this.state.formSettings.onChangeBeforeValidation || {};
|
||||
|
||||
const formState = previousState.formState.withMutations(mutState => {
|
||||
mutState.update('data', stateData => stateData.withMutations(mutStateData => {
|
||||
if (typeof onChangeBeforeValidationCallback === 'object') {
|
||||
if (onChangeBeforeValidationCallback[key]) {
|
||||
onChangeBeforeValidationCallback[key](mutStateData, key, oldValue, value);
|
||||
}
|
||||
} else {
|
||||
onChangeBeforeValidationCallback(mutStateData, key, oldValue, value);
|
||||
}
|
||||
|
||||
mutStateData.setIn([key, 'value'], value);
|
||||
}));
|
||||
|
||||
validateFormState(this, mutState);
|
||||
});
|
||||
|
||||
let newState = {
|
||||
formState: previousState.formState.withMutations(mutState => {
|
||||
mutState.setIn(['data', key, 'value'], value);
|
||||
validateFormState(this, mutState);
|
||||
})
|
||||
formState
|
||||
};
|
||||
|
||||
|
||||
const onChangeCallback = this.state.formSettings.onChange || {};
|
||||
|
||||
if (typeof onChangeCallback === 'object') {
|
||||
|
|
@ -1000,6 +1129,7 @@ export {
|
|||
InputField,
|
||||
CheckBox,
|
||||
TextArea,
|
||||
DatePicker,
|
||||
Dropdown,
|
||||
AlignedRow,
|
||||
ButtonRow,
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
.mt-button-row > * {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.mt-button-row > *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mt-form-status {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.mt-action-links > * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mt-action-links > *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.form-horizontal .control-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mt-form-disabled {
|
||||
background-color: #eeeeee;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.mt-tableselect-dropdown {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.mt-tableselect-table.mt-tableselect-table-hidden {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.mt-tableselect-dropdown input[readonly] {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
h3.legend {
|
||||
font-size: 21px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mt-secondary-nav {
|
||||
margin-top: 5px;
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.mt-secondary-nav {
|
||||
margin: 0px;
|
||||
background-color: #f5f5f5;
|
||||
padding: 5px 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.mt-secondary-nav > li {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.mt-secondary-nav > li > a {
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
|
@ -5,12 +5,12 @@ import { translate } from 'react-i18next';
|
|||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router';
|
||||
import {BrowserRouter as Router, Route, Link, Switch, Redirect} from 'react-router-dom'
|
||||
import './page.css';
|
||||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
import { DismissibleAlert, Button } from './bootstrap-components';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import axios from '../lib/axios';
|
||||
import styles from "./styles.scss";
|
||||
|
||||
|
||||
class Breadcrumb extends Component {
|
||||
|
|
@ -133,7 +133,7 @@ class SecondaryNavBar extends Component {
|
|||
}
|
||||
|
||||
if (renderedElems.length > 1) {
|
||||
let className = 'mt-secondary-nav nav nav-pills';
|
||||
let className = styles.secondaryNav + ' nav nav-pills';
|
||||
if (this.props.className) {
|
||||
className += ' ' + this.props.className;
|
||||
}
|
||||
|
|
@ -482,7 +482,7 @@ class Toolbar extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
let className = 'pull-right mt-button-row';
|
||||
let className = 'pull-right ' + styles.buttonRow;
|
||||
if (this.props.className) {
|
||||
className += ' ' + this.props.className;
|
||||
}
|
||||
|
|
|
|||
89
client/src/lib/styles.scss
Normal file
89
client/src/lib/styles.scss
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
:global .DayPicker {
|
||||
border-left: 1px solid lightgray;
|
||||
border-right: 1px solid lightgray;
|
||||
border-bottom: 1px solid lightgray;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.dayPickerWrapper {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.buttonRow > * {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.buttonRow > *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.formStatus {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.actionLinks > * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.actionLinks > *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
:global .form-horizontal .control-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.formDisabled {
|
||||
background-color: #eeeeee;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:global .ace_editor {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.tableSelectDropdown {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tableSelectTable.tableSelectTableHidden {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.tableSelectDropdown input[readonly] {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
:global h3.legend {
|
||||
font-size: 21px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.secondaryNav {
|
||||
margin-top: 5px;
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.secondaryNav {
|
||||
margin: 0px;
|
||||
background-color: #f5f5f5;
|
||||
padding: 5px 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.secondaryNav > li {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.secondaryNav > li > a {
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import axios from './axios';
|
|||
|
||||
import { withPageHelpers } from '../lib/page'
|
||||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||
import styles from "./styles.scss";
|
||||
|
||||
//dtFactory();
|
||||
//dtSelectFactory();
|
||||
|
|
@ -169,7 +170,7 @@ class Table extends Component {
|
|||
|
||||
this.selectionMap = nextSelectionMap;
|
||||
|
||||
return updateDueToSelectionChange || this.props.data != nextProps.data || this.props.dataUrl != nextProps.dataUrl;
|
||||
return updateDueToSelectionChange || this.props.data !== nextProps.data || this.props.dataUrl !== nextProps.dataUrl;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -179,7 +180,7 @@ class Table extends Component {
|
|||
for (const column of columns) {
|
||||
if (column.actions) {
|
||||
const createdCellFn = (td, data, rowData) => {
|
||||
const linksContainer = jQuery('<span class="mt-action-links"/>');
|
||||
const linksContainer = jQuery(`<span class="${styles.actionLinks}"/>`);
|
||||
|
||||
let actions = column.actions(rowData);
|
||||
let options = {};
|
||||
|
|
@ -322,19 +323,20 @@ class Table extends Component {
|
|||
if (this.props.data) {
|
||||
this.table.clear();
|
||||
this.table.rows.add(this.props.data);
|
||||
|
||||
} else {
|
||||
const self = this;
|
||||
this.table.rows().every(function() {
|
||||
const key = this.data()[self.props.selectionKeyIndex];
|
||||
if (self.selectionMap.has(key)) {
|
||||
jQuery(this.node()).addClass('selected');
|
||||
} else {
|
||||
jQuery(this.node()).removeClass('selected');
|
||||
}
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this.table.rows().every(function() {
|
||||
const key = this.data()[self.props.selectionKeyIndex];
|
||||
if (self.selectionMap.has(key)) {
|
||||
jQuery(this.node()).addClass('selected');
|
||||
} else {
|
||||
jQuery(this.node()).removeClass('selected');
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSelectInfo();
|
||||
this.fetchAndNotifySelectionData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import axios from './axios';
|
|||
|
||||
import { withPageHelpers } from '../lib/page'
|
||||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||
import styles from "./styles.scss";
|
||||
|
||||
const TreeSelectMode = {
|
||||
NONE: 0,
|
||||
|
|
@ -122,7 +123,7 @@ class TreeTable extends Component {
|
|||
}
|
||||
|
||||
if (this.props.actions) {
|
||||
const linksContainer = jQuery('<span class="mt-action-links"/>');
|
||||
const linksContainer = jQuery(`<span class="${styles.actionLinks}"/>`);
|
||||
|
||||
const actions = this.props.actions(node);
|
||||
for (const {label, link} of actions) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue