UNPKG

instantjob-recruiter-client

Version:

a set of tools for creating an instantjob recruiter react client

446 lines (412 loc) 13 kB
import React, {Component} from 'react' import classNames from 'classnames/bind' import InfiniteList from './infinite_list' import {DragSource, DropTarget} from 'react-dnd' import {getEmptyImage} from 'react-dnd-html5-backend' import {MdSubdirectoryArrowRight} from 'react-icons/lib/md' import {color} from '../common/constants' import {range, capitalize_first_letter, array_contains, make_memoized} from '../common/utilities' import moment from '../common/moment' import auto_bind from '../common/auto_bind' import {event_intersects} from '../common/calendar' import {tolerant_equal, tolerant_selector} from '../selectors/base' const cx = classNames.bind(require('../styles/input_calendar.scss')) let get_start_end = (start_id, end_id) => { let start_of_week = moment().startOf('week') let start = start_id, end = end_id if (start > end) { start = end end = start_id } return { start: moment(start_of_week).add(start - 1, 'days'), end: moment(start_of_week).add(end - 1, 'days').endOf('day'), } } let get_events = (props, state) => { if (state.is_dragging && props.drag_color) { if (state.paint) { return {...props.events, [props.drag_color]: [...(props.events[props.drag_color] || []), { id: 'temp', ...get_start_end(state.start, state.end), full_days: true }]} } else { let other_events = {...props.events} if (other_events[props.drag_color]) { delete other_events[props.drag_color] } return {...other_events, [""]: [{ id: 'temp', ...get_start_end(state.start, state.end), full_days: true, }], [props.drag_color]: props.events[props.drag_color] || []} } } else { return props.events } } let make_get_week_events = make_memoized((week_number) => tolerant_selector( [get_events], (events) => { let week = moment().startOf('week').add(week_number, 'weeks') let hues = Object.keys(events) return hues.reduce((other_events, hue) => ({ ...other_events, [hue]: events[hue].filter((e) => event_intersects(e, moment(week).subtract(1, 'day'), 9, 'days')) }), {}) } )) let make_get_week_days = make_memoized((week_number) => tolerant_selector( [make_get_week_events(week_number)], (events) => { let week = moment().startOf('week').add(week_number, 'weeks') let days = range(9).map((day) => { let date = moment(week).add(day - 1, 'days') let hued_event = Object.keys(events).reduce((hued_event, hue) => { if (hued_event) { return hued_event } else { return events[hue].reduce((hued_event, e) => { if (hued_event) { return hued_event } else { if (event_intersects(e, date, 1, 'day')) { return {event: e, hue} } else { return null } } }, null) } }, null) return {date, ...hued_event} }) const days_event_are_continuous = (i) => days[i].hue == days[i+1].hue for (let i = 0; i < 7; i ++) { let category if (days[i+1].event) { if (days_event_are_continuous(i)) { if (days_event_are_continuous(i+1)) { category = "middle" } else { category = "end" } } else { if (days_event_are_continuous(i+1)) { category = "start" } else { category = "one" } } } else { category = "none" } days[i+1].category = category } delete days[0] delete days[8] return days } )) class Calendar extends Component { constructor(props) { super(props) auto_bind(this) } componentDidMount() { this.update_month(moment()) } scroll_to_week(week) { this.weeks.scroll_to_week(week) } render() { return ( <div className={cx('input-calendar')} style={{display: "flex", flexDirection: 'column', alignItems: 'stretch', flex: 1}}> <div style={{flexShrink: 0, height: 30, display: "flex", flexDirection: "column", justifyContent: 'center', alignItems: "center"}}> <div ref={(month_label) => this.month_label = month_label} style={{textAlign: 'center', color: color('primary', 'light'), fontWeight: 900}}> </div> </div> <div style={{marginLeft: this.props.hide_navigation ? 0 : 50, flexShrink: 0, height: 30, display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "space-around", borderBottomStyle: "solid", borderBottomWidth: 2, borderBottomColor: color('primary', 'light')}}> {["L", "M", "M", "J", "V", "S", "D"].map((day, index) => <WeekDay day={day} key={index} />)} </div> <div style={{flex: 1, cursor: this.props.drag_color ? 'ew-resize' : 'initial', display: 'flex', alignItems: 'stretch'}}> <Weeks ref={(weeks) => this.weeks = weeks} update_month={this.update_month} events={this.props.events} on_click={this.props.on_click} drag_color={this.props.drag_color} on_drag_and_drop={this.props.on_drag_and_drop} on_navigate_to_week={this.props.on_navigate_to_week} hide_navigation={this.props.hide_navigation} /> </div> </div> ) } update_month(month) { $(this.month_label).text(capitalize_first_letter(month.isSame(moment(), 'year') ? month.format("MMMM") : month.format("MMMM YYYY"))) } } class Weeks extends Component { constructor(props) { super(props) this.state = { is_dragging: false, start: 0, end: 0, paint: true, } auto_bind(this) this.current_month = moment().startOf('month') } is_week_loaded(week) { return week < this.state.week_count - this.state.week_offset } on_week_click(date, event) { this.props.on_click(date, event) } drag_start(day, paint) { this.setState({start: day, end: day, is_dragging: true, paint}) } drag_hover(day) { this.setState({end: day}) } drag_end(day) { this.setState({is_dragging: false}) let {start, end} = get_start_end(this.state.start, this.state.end) this.props.on_drag_and_drop(start, end, this.state.paint) } scroll_to_week(week) { this.list.scroll_to_item(week) } handle_scroll(scrollTop) { let month = moment().startOf('week').add(scrollTop / row_height, 'weeks') if (!month.isSame(this.current_month, 'month')) { this.current_month = month this.props.update_month(month) } } render_week(index) { return ( <Week key={index} days={make_get_week_days(index)(this.props, this.state)} on_click={this.on_week_click} week={index} drag_start={this.drag_start} drag_hover={this.drag_hover} drag_end={this.drag_end} on_navigate_to_week={this.props.on_navigate_to_week} hide_navigation={this.props.hide_navigation} /> ) } render() { return ( <InfiniteList item_height={row_height} handle_scroll={this.handle_scroll} ref={(list) => this.list = list} > {this.render_week} </InfiniteList> ) } } class WeekDay extends Component { render() { return ( <span style={{color: color('primary', 'light'), fontWeight: 900}}> {this.props.day} </span> ) } } class Week extends Component { constructor(props) { super(props) auto_bind(this) } on_navigate_to_week() { this.props.on_navigate_to_week(this.props.week) } render() { return ( <div className={cx('input-calendar__week')} style={{height: 50, width: this.props.width, display: 'flex', flexDirection: 'row', alignItems: 'stretch', justifyContent: 'space-around'}}> {this.props.hide_navigation ? null : ( <div onClick={this.on_navigate_to_week} className={cx('input-calendar__week-link')}> <MdSubdirectoryArrowRight /> </div> )} {this.props.days.map((day, index) => ( <DragDropDay drag_start={this.props.drag_start} drag_hover={this.props.drag_hover} drag_end={this.props.drag_end} day={this.props.week * 7 + index} event={day.event} hue={day.hue} category={day.category} on_click={this.props.on_click} date={day.date} key={index} /> ))} </div> ) } } class Day extends Component { shouldComponentUpdate(props) { return props.day !== this.props.day || props.event !== this.props.event || props.hue !== this.props.hue || props.category !== this.props.category || !props.date.isSame(this.props.date) } componentDidMount() { this.props.connectDragPreview(getEmptyImage(), {captureDraggingState: true}) } render() { let hue switch (this.props.category) { case "none": hue = color('primary') break default: hue = this.props.hue ? 'white' : color('primary') } return this.props.connectDropTarget(this.props.connectDragSource( <div onClick={() => this.props.on_click(this.props.date, this.props.event)} style={{flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', backgroundColor: this.props.date.month() % 2 == 0 ? 'white' : color('black', 'pale')}}> <Sticker category={this.props.category} hue={this.props.hue} /> <div style={{color: hue, zIndex: 1}}> {this.props.date.format("D")} </div> {this.props.date.date() == 1 ? ( <div style={{color: this.props.hue ? this.props.hue : hue, zIndex: 1, fontSize: 10, position: 'absolute', bottom: 1, left: 0, right: 0, textAlign: 'center'}}> {this.props.date.format("MMM")} </div> ) : null} </div> )) } } const DragDropDay = DropTarget( "generic-calendar__day", { hover(props, monitor, component) { let period = monitor.getItem() if (period) { if (period.end != props.day) { props.drag_hover(props.day) period.end = props.day } } }, }, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), }) )(DragSource( "generic-calendar__day", { beginDrag(props) { props.drag_start(props.day, !props.event) return {start: props.day, end: props.day} }, endDrag(props) { props.drag_end() } }, (connect, monitor) => ({ connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging(), }) )(Day)) class Sticker extends Component { render() { switch (this.props.category) { case 'one': return ( <div style={styles.sticker_container}> <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} /> </div> ) case 'middle': return ( <div style={styles.sticker_container}> <div style={{...styles.middle_sticker, backgroundColor: this.props.hue}} /> </div> ) case 'end': return ( <div style={styles.sticker_container}> <div style={{...styles.filler, justifyContent: 'flex-start'}}> <div style={{...styles.half_sticker, backgroundColor: this.props.hue}} /> <div style={styles.half_sticker} /> </div> <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} /> </div> ) case 'start': return ( <div style={styles.sticker_container}> <div style={{...styles.filler, justifyContent: 'flex-end'}}> <div style={styles.half_sticker} /> <div style={{...styles.half_sticker, backgroundColor: this.props.hue}} /> </div> <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} /> </div> ) default: return <div /> } } } const row_height = 50 const diameter = 24 const styles = { sticker_container: { position: 'absolute', left: 0, right: 0, top: (row_height - diameter)/2, bottom: (row_height - diameter)/2, display: 'flex', alignItems: 'stretch', flexDirection: 'row', justifyContent: 'center', }, middle_sticker: { flex: 1, }, filler: { display: 'flex', position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, flexDirection: 'row', alignItems: 'stretch', }, half_sticker: { flex: 1, }, one_sticker: { borderRadius: diameter/2, height: diameter, width: diameter, }, } Calendar.defaultProps = { events: {}, on_click: () => {}, drag_color: '', on_drag_and_drop: (start, end, paint) => {}, hide_navigation: false, } export default Calendar