diff --git a/client/package.json b/client/package.json
index ee60f1ba..ac7d1de3 100644
--- a/client/package.json
+++ b/client/package.json
@@ -27,6 +27,7 @@
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-ace": "^5.1.0",
+ "react-day-picker": "^6.1.0",
"react-dom": "^15.6.1",
"react-i18next": "^4.6.1",
"react-router-dom": "^4.1.1",
diff --git a/client/src/lib/form.js b/client/src/lib/form.js
index b1fd8547..3d07b5a4 100644
--- a/client/src/lib/form.js
+++ b/client/src/lib/form.js
@@ -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
{t('Loading ...')}
+ return {t('Loading ...')}
} else {
return ;
}
@@ -91,7 +99,7 @@ class Form extends Component {
{statusMessageText &&
- {statusMessageText}
+ {statusMessageText}
}
@@ -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 (
+
+ {months[date.getMonth()]}
+
+ );
+ }
+
+ 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 = ;
+ 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,
+
+
+ owner.updateFormValue(id, evt.target.value)}/>
+
+
+ {this.state.opened &&
+
+ this.daySelected(date)}
+ selectedDays={selectedDate}
+ initialMonth={selectedDate}
+ fromMonth={fromMonth}
+ toMonth={toMonth}
+ captionElement={captionElement}
+ />
+
+ }
+
+ );
+ }
+}
+
+
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,
-
+
-
+
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}/>
@@ -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,
diff --git a/client/src/lib/page.css b/client/src/lib/page.css
deleted file mode 100644
index e67794fa..00000000
--- a/client/src/lib/page.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/client/src/lib/page.js b/client/src/lib/page.js
index 53227edd..a911950c 100644
--- a/client/src/lib/page.js
+++ b/client/src/lib/page.js
@@ -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;
}
diff --git a/client/src/lib/styles.scss b/client/src/lib/styles.scss
new file mode 100644
index 00000000..bd746f95
--- /dev/null
+++ b/client/src/lib/styles.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/client/src/lib/table.js b/client/src/lib/table.js
index 351569b9..44ae9073 100644
--- a/client/src/lib/table.js
+++ b/client/src/lib/table.js
@@ -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('');
+ const linksContainer = jQuery(``);
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();
}
diff --git a/client/src/lib/tree.js b/client/src/lib/tree.js
index bbca04c0..28ebe61b 100644
--- a/client/src/lib/tree.js
+++ b/client/src/lib/tree.js
@@ -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('');
+ const linksContainer = jQuery(``);
const actions = this.props.actions(node);
for (const {label, link} of actions) {
diff --git a/client/src/lists/fields/CUD.js b/client/src/lists/fields/CUD.js
index 433697ed..6f1230ca 100644
--- a/client/src/lists/fields/CUD.js
+++ b/client/src/lists/fields/CUD.js
@@ -14,7 +14,8 @@ import { getFieldTypes } from './field-types';
import interoperableErrors from '../../../../shared/interoperable-errors';
import validators from '../../../../shared/validators';
import slugify from 'slugify';
-import { parseDate, parseBirthday } from '../../../../shared/fields';
+import { parseDate, parseBirthday, DateFormat } from '../../../../shared/date';
+import styles from "../../lib/styles.scss";
@translate()
@withForm
@@ -72,7 +73,7 @@ export default class CUD extends Component {
}
data.enumOptions = '';
- data.dateFormat = 'eur';
+ data.dateFormat = DateFormat.EUR;
data.renderTemplate = '';
switch (data.type) {
@@ -374,8 +375,8 @@ export default class CUD extends Component {