web2019/static/js/shop/Layout.jsx
Egor Savkin ecbbd6898d Split components into separate files
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-01-09 10:14:53 +08:00

273 lines
9.8 KiB
JavaScript

import React, {PureComponent} from 'react';
import PropTypes from "prop-types";
/**
* Component that provides a base layout (aside/main) for the page.
*/
export class Layout extends PureComponent {
static get propTypes() {
return {
aside: PropTypes.any,
main: PropTypes.any,
mobileSideMenuShouldOpen: PropTypes.bool,
isMobile: PropTypes.bool,
newCardJustAdded: PropTypes.bool,
onClickToggleMobileSideMenu: PropTypes.func,
onClickCloseRFQFeedback: PropTypes.func,
RFQBodyType: PropTypes.string,
RFQBodyOrder: PropTypes.string,
onClickLoadCustomConf: PropTypes.func,
items: PropTypes.object,
};
}
static get defaultProps() {
return {
mobileSideMenuShouldOpen: false,
};
}
constructor(props) {
super(props);
this.state = {
customconf: '',
error: null,
};
this.handleCustomConfig = this.handleCustomConfig.bind(this);
this.handleClickLoad = this.handleClickLoad.bind(this);
this.checkValidation = this.checkValidation.bind(this);
// retrieve list of available pn
const items_keys = Object.keys(props.items);
this.list_pn = items_keys.map(function (key) {
return props.items[key].name_number;
});
}
handleCustomConfig(e) {
const value = e.target.value;
this.checkValidation(value);
}
checkValidation(conf) {
let conf_obj;
try {
conf_obj = JSON.parse(conf);
} catch (e) {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: 'invalid format',
});
}
if (!conf_obj) {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: 'invalid format',
});
}
if ((!conf_obj.items || !conf_obj.type) &&
(Object.prototype.toString.call(conf_obj.items) !== '[object Array]' ||
Object.prototype.toString.call(conf_obj.type) !== '[object String]')) {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: 'invalid format',
});
}
if (conf_obj.type !== "desktop" && conf_obj.type !== "rack") {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: 'invalid format',
});
}
conf_obj.items.map(function (item) {
try {
return JSON.parse(item);
} catch (e) {
return null;
}
});
conf_obj.items = conf_obj.items.filter(function (item) {
return item;
});
if (conf_obj.items.filter(function (item) {
return Object.prototype.toString.call(item) !== '[object Object]' || !item.pn || Object.prototype.toString.call(item.pn) !== '[object String]';
}).length > 0) {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: 'invalid format',
});
}
conf_obj.items = conf_obj.items.map(function (item) {
return {
pn: item.pn,
options: item.options ? item.options : null,
};
});
const self = this;
const unknow_pn = conf_obj.items.filter(function (item_pn) {
return self.list_pn.includes(item_pn.pn) === false;
}).map(function (item_pn) {
return item_pn.pn;
});
if (unknow_pn.length > 0) {
return this.setState({
...this.state,
customconf: conf,
customconf_ready: null,
error: `${unknow_pn.join(', ')} unknown${unknow_pn.length > 1 ? 's':''} pn number`,
});
}
this.setState({
...this.state,
customconf: conf,
error: null,
customconf_ready: conf_obj,
});
}
handleClickLoad() {
this.checkValidation(this.state.customconf);
if (this.props.onClickLoadCustomConf) {
this.props.onClickLoadCustomConf(this.state.customconf_ready);
}
}
render() {
const {
aside,
main,
mobileSideMenuShouldOpen,
isMobile,
newCardJustAdded,
onClickToggleMobileSideMenu,
onClickCloseRFQFeedback,
showRFQFeedback,
RFQBodyType,
RFQBodyOrder,
} = this.props;
return (
<div className="layout">
<aside className={'aside ' + (mobileSideMenuShouldOpen ? 'menu-opened' : '')}>{aside}</aside>
{mobileSideMenuShouldOpen ? (
<section className="main" onClick={onClickToggleMobileSideMenu}>{main}</section>
) : (
<section className="main">{main}</section>
)}
{isMobile && newCardJustAdded ? (
<div className="feedback-add-success">
added
</div>
) : null}
<div className={`modal fade ${ showRFQFeedback ? 'show': ''}`} style={{'display': showRFQFeedback ? 'block':'none'}} id="exampleModal" tabIndex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-body rfqFeedback">
<div className="d-flex w-100">
{RFQBodyType === 'email' ? (
<div className="d-flex">
<div>
<img width="30px" src="/images/shop/icon-done.svg" alt="close" />
</div>
<div style={{'padding': '0 .5em'}}>
We've received your request and will be in contact soon.
</div>
</div>
) : null }
{RFQBodyType === 'show' ? (
<p>
{RFQBodyOrder}
</p>
) : null}
{RFQBodyType === 'import' ? (
<div className="w-100">
<form className="w-100">
<div className="mb-3">
<p className="small">
Input the JSON description below. Should be something like:
<br />
{JSON.stringify({"items":[{"pn":"1124"},{"pn":"2118"},{"pn":"2118"},{"pn":"2128"}],"type":"desktop"})}
</p>
</div>
<div className="mb-3 w-100">
<textarea
onChange={this.handleCustomConfig}
value={this.state.customconf}
className="form-control w-100"
rows="5"
placeholder="Input JSON description here." />
</div>
{this.state.error ? (
<div className="mb-3">
<p className="text-danger">{this.state.error}</p>
</div>
) : null}
</form>
<div className="d-flex flex-column flex-sm-row justify-content-end">
<a type="button" onClick={onClickCloseRFQFeedback} className="btn btn-sm btn-outline-primary m-0 mb-2 mb-sm-0 me-sm-2">Close</a>
<a type="button" onClick={this.handleClickLoad} className={`btn btn-sm btn-primary m-0 ms-sm-2 ${this.state.error ? 'disabled':''}`}>Load configuration</a>
</div>
</div>
) : null}
<div>
<button onClick={onClickCloseRFQFeedback}>
<img src="/images/shop/icon-close.svg" alt="close" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div onClick={onClickCloseRFQFeedback} className={`modal-backdrop fade ${ showRFQFeedback ? 'show': ''}`} style={{'display': showRFQFeedback ? 'initial':'none'}}></div>
</div>
);
}
}