diff options
Diffstat (limited to 'front/odiparpack/app/components/Calendar')
5 files changed, 582 insertions, 0 deletions
diff --git a/front/odiparpack/app/components/Calendar/AddEvent.js b/front/odiparpack/app/components/Calendar/AddEvent.js new file mode 100644 index 0000000..ef1f5a5 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/AddEvent.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Add from '@material-ui/icons/Add'; +import { Fab, Tooltip } from '@material-ui/core'; +import FloatingPanel from '../Panel/FloatingPanel'; +import AddEventForm from './AddEventForm'; +import styles from './calendar-jss.js'; + + +class AddEvent extends React.Component { + showResult(values) { + setTimeout(() => { + this.props.submit(values); + }, 500); // simulate server latency + } + + render() { + const { + classes, + openForm, + closeForm, + addEvent + } = this.props; + const branch = ''; + return ( + <div> + <Tooltip title="Add New Event"> + <Fab color="secondary" onClick={() => addEvent()} className={classes.addBtn}> + <Add /> + </Fab> + </Tooltip> + <FloatingPanel title="Add New Event" openForm={openForm} branch={branch} closeForm={() => closeForm()}> + <AddEventForm onSubmit={(values) => this.showResult(values)} /> + </FloatingPanel> + </div> + ); + } +} + +AddEvent.propTypes = { + classes: PropTypes.object.isRequired, + openForm: PropTypes.bool.isRequired, + addEvent: PropTypes.func.isRequired, + closeForm: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(AddEvent); diff --git a/front/odiparpack/app/components/Calendar/AddEventForm.js b/front/odiparpack/app/components/Calendar/AddEventForm.js new file mode 100644 index 0000000..9edccca --- /dev/null +++ b/front/odiparpack/app/components/Calendar/AddEventForm.js @@ -0,0 +1,178 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import { reduxForm, Field } from 'redux-form/immutable'; +import { connect } from 'react-redux'; +import css from 'ba-styles/Form.scss'; +import { Button, Radio, RadioGroup, FormLabel, FormControlLabel } from '@material-ui/core'; +import { TextFieldRedux } from '../Forms/ReduxFormMUI'; +import styles from './calendar-jss'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); + +const DateTimePickerRow = props => { + const { + showErrorsInline, + dispatch, + input: { onChange, value }, + meta: { touched, error }, + ...other + } = props; + + const showError = showErrorsInline || touched; + return ( + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + error={!!(showError && error)} + helperText={showError && error} + value={value || new Date()} + onChange={onChange} + disablePast + label="DateTimePicker" + {...other} + /> + </MuiPickersUtilsProvider> + ); +}; + +DateTimePickerRow.propTypes = { + showErrorsInline: PropTypes.bool, + dispatch: PropTypes.func, + input: PropTypes.object.isRequired, + meta: PropTypes.object.isRequired, +}; + +const renderRadioGroup = ({ input, ...rest }) => ( + <RadioGroup + {...input} + {...rest} + valueselected={input.value} + onChange={(event, value) => input.onChange(value)} + /> +); + +renderRadioGroup.propTypes = { + input: PropTypes.object.isRequired, +}; + +DateTimePickerRow.defaultProps = { + showErrorsInline: false, + dispatch: () => {}, +}; + +class AddEventForm extends React.Component { + state = { + selectedDate: new Date(), + } + + onChangeDate = date => { + this.setState({ selectedDate: date }); + } + + saveRef = ref => { + this.ref = ref; + return this.ref; + }; + + render() { + const { + classes, + reset, + pristine, + submitting, + handleSubmit, + } = this.props; + const { selectedDate } = this.state; + return ( + <div> + <form onSubmit={handleSubmit}> + <section className={css.bodyForm}> + <div> + <Field + name="title" + component={TextFieldRedux} + placeholder="Event Name" + label="Event Name" + validate={required} + required + ref={this.saveRef} + className={classes.field} + /> + </div> + <div> + <Field + name="start" + component={DateTimePickerRow} + placeholder="Start Date" + value={selectedDate} + onChange={this.onChangeDate} + label="Start Date" + className={classes.field} + /> + </div> + <div> + <Field + name="end" + component={DateTimePickerRow} + placeholder="End Date" + value={selectedDate} + onChange={this.onChangeDate} + label="End Date" + className={classes.field} + /> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Label Color</FormLabel> + <Field name="hexColor" className={classes.inlineWrap} component={renderRadioGroup}> + <FormControlLabel value="F8BBD0" control={<Radio className={classes.redRadio} classes={{ root: classes.redRadio, checked: classes.checked }} />} label="Red" /> + <FormControlLabel value="C8E6C9" control={<Radio className={classes.greenRadio} classes={{ root: classes.greenRadio, checked: classes.checked }} />} label="Green" /> + <FormControlLabel value="B3E5FC" control={<Radio className={classes.blueRadio} classes={{ root: classes.blueRadio, checked: classes.checked }} />} label="Blue" /> + <FormControlLabel value="D1C4E9" control={<Radio className={classes.violetRadio} classes={{ root: classes.violetRadio, checked: classes.checked }} />} label="Violet" /> + <FormControlLabel value="FFECB3" control={<Radio className={classes.orangeRadio} classes={{ root: classes.orangeRadio, checked: classes.checked }} />} label="Orange" /> + </Field> + </div> + </section> + <div className={css.buttonArea}> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </div> + ); + } +} + +AddEventForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const AddEventFormRedux = reduxForm({ + form: 'immutableAddCalendar', + enableReinitialize: true, +})(AddEventForm); + +const reducer = 'calendar'; +const AddEventInit = connect( + state => ({ + force: state, + initialValues: state.getIn([reducer, 'formValues']) + }), +)(AddEventFormRedux); + +export default withStyles(styles)(AddEventInit); diff --git a/front/odiparpack/app/components/Calendar/DetailEvent.js b/front/odiparpack/app/components/Calendar/DetailEvent.js new file mode 100644 index 0000000..b25d8b9 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/DetailEvent.js @@ -0,0 +1,167 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import Today from '@material-ui/icons/Today'; +import { Typography, IconButton, Menu, MenuItem, Divider, Popover } from '@material-ui/core'; +import styles from './calendar-jss'; + + +const ITEM_HEIGHT = 48; + +class DetailEvent extends React.Component { + state = { + anchorElOpt: null, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + handleDeleteEvent = (event) => { + this.setState({ anchorElOpt: null }); + this.props.remove(event); + this.props.close(); + }; + + render() { + const getDate = date => { + if (date._isAMomentObject) { + return date.format('MMMM Do YYYY'); + } + let dd = date.getDate(); + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + const mm = monthNames[date.getMonth()]; // January is 0! + const yyyy = date.getFullYear(); + + if (dd < 10) { + dd = '0' + dd; + } + + const convertedDate = mm + ', ' + dd + ' ' + yyyy; + + return convertedDate; + }; + + const getTime = time => { + if (time._isAMomentObject) { + return time.format('LT'); + } + let h = time.getHours(); + let m = time.getMinutes(); + + if (h < 10) { + h = '0' + h; + } + + if (m < 10) { + m = '0' + m; + } + + const convertedTime = h + ':' + m; + return convertedTime; + }; + + const { + classes, + anchorEl, + event, + close, + anchorPos + } = this.props; + const { anchorElOpt } = this.state; + return ( + <Popover + open={anchorEl} + anchorReference="anchorPosition" + anchorPosition={anchorPos} + className={classes.eventDetail} + onClose={close} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.moreOpt} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + {event !== null + && ( + <Fragment> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + <MenuItem onClick={() => this.handleDeleteEvent(event)}> + Delete Event + </MenuItem> + </Menu> + <Typography variant="h5" style={{ background: `#${event.hexColor}` }} className={classes.eventName}> + <Today /> + {' '} + {event.title} + </Typography> + <div className={classes.time}> + <Typography> +Start: + {getDate(event.start)} + {' '} +- + {getTime(event.start)} + </Typography> + <Divider className={classes.divider} /> + <Typography> +End: + {getDate(event.end)} + {' '} +- + {getTime(event.end)} + </Typography> + </div> + </Fragment> + ) + } + </Popover> + ); + } +} + +DetailEvent.propTypes = { + classes: PropTypes.object.isRequired, + anchorEl: PropTypes.bool.isRequired, + anchorPos: PropTypes.object.isRequired, + event: PropTypes.object, + close: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, +}; + +DetailEvent.defaultProps = { + event: null, +}; + +export default withStyles(styles)(DetailEvent); diff --git a/front/odiparpack/app/components/Calendar/EventCalendar.js b/front/odiparpack/app/components/Calendar/EventCalendar.js new file mode 100644 index 0000000..fe7b76e --- /dev/null +++ b/front/odiparpack/app/components/Calendar/EventCalendar.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import BigCalendar from 'react-big-calendar'; +import moment from 'moment'; +import { Paper } from '@material-ui/core'; +import styles from './calendar-jss'; + +BigCalendar.setLocalizer(BigCalendar.momentLocalizer(moment)); + +function Event(event) { + return ( + <span className="eventBlock">{event.title}</span> + ); +} + +class EventCalendar extends React.Component { + eventStyleGetter = event => { + const backgroundColor = '#' + event.hexColor; + const style = { + backgroundColor, + }; + return { + style + }; + } + + render() { + const allViews = Object.keys(BigCalendar.Views).map(k => BigCalendar.Views[k]); + const { + classes, + events, + handleEventClick + } = this.props; + return ( + <Paper className={classes.root}> + <BigCalendar + className={classes.calendarWrap} + selectable + events={events} + defaultView="month" + views={allViews} + step={60} + showMultiDayTimes + scrollToTime={new Date(1970, 1, 1, 6)} + defaultDate={new Date(2015, 3, 12)} + onSelectEvent={(selectedEvent) => handleEventClick(selectedEvent)} + eventPropGetter={(this.eventStyleGetter)} + onSelectSlot={slotInfo => console.log( + `selected slot: \n\nstart ${slotInfo.start.toLocaleString()} ` + + `\nend: ${slotInfo.end.toLocaleString()}` + + `\naction: ${slotInfo.action}` + ) + } + components={{ + event: Event + }} + /> + </Paper> + ); + } +} + +EventCalendar.propTypes = { + classes: PropTypes.object.isRequired, + events: PropTypes.array.isRequired, + handleEventClick: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(EventCalendar); diff --git a/front/odiparpack/app/components/Calendar/calendar-jss.js b/front/odiparpack/app/components/Calendar/calendar-jss.js new file mode 100644 index 0000000..36ed0a4 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/calendar-jss.js @@ -0,0 +1,118 @@ +import { + pink as red, + lightGreen as green, + lightBlue as blue, + deepPurple as violet, + orange, +} from '@material-ui/core/colors'; + +const styles = theme => ({ + root: { + padding: 20, + [theme.breakpoints.down('sm')]: { + padding: '20px 8px' + }, + }, + calendarWrap: { + minHeight: 600 + }, + addBtn: { + position: 'fixed', + bottom: 30, + right: 30, + zIndex: 100 + }, + typography: { + margin: theme.spacing(2), + }, + divider: { + margin: '5px 0', + textAlign: 'center' + }, + button: { + margin: theme.spacing(1), + }, + eventName: { + padding: '50px 20px 10px 30px', + minWidth: 400, + color: 'rgba(0, 0, 0, 0.7)', + '& svg': { + top: -2, + position: 'relative' + } + }, + time: { + padding: 20 + }, + moreOpt: { + position: 'absolute', + top: 10, + right: 10 + }, + field: { + width: '100%', + marginBottom: 20 + }, + fieldBasic: { + width: '100%', + marginBottom: 20, + marginTop: 10 + }, + inlineWrap: { + display: 'flex', + flexDirection: 'row' + }, + redRadio: { + color: red[600], + '& svg': { + borderRadius: '50%', + background: red[100], + }, + '&$checked': { + color: red[500], + }, + }, + greenRadio: { + color: green[600], + '& svg': { + borderRadius: '50%', + background: green[100], + }, + '&$checked': { + color: green[500], + }, + }, + blueRadio: { + color: blue[600], + '& svg': { + borderRadius: '50%', + background: blue[100], + }, + '&$checked': { + color: blue[500], + }, + }, + violetRadio: { + color: violet[600], + '& svg': { + borderRadius: '50%', + background: violet[100], + }, + '&$checked': { + color: violet[500], + }, + }, + orangeRadio: { + color: orange[600], + '& svg': { + borderRadius: '50%', + background: orange[100], + }, + '&$checked': { + color: orange[500], + }, + }, + checked: {}, +}); + +export default styles; |
