instantjob-recruiter-client
Version:
a set of tools for creating an instantjob recruiter react client
312 lines (287 loc) • 9.14 kB
JSX
import React, {Component} from 'react'
import classNames from 'classnames/bind'
import {connect} from 'react-redux'
import {MdArrowBack, MdKeyboardArrowLeft, MdKeyboardArrowRight} from 'react-icons/lib/md'
import Calendar from '../generic_calendar'
import Button from 'components/utils/button'
import TimePickers from './time_pickers'
import {color} from '../../common/constants'
import {
array_from_hash,
map_hash,
set_from_array,
array_from_set,
range,
empty_set,
for_all,
filter_hash,
flatten_array,
create_hash,
} from '../../common/utilities'
import request from '../../common/request'
import store from '../../common/store'
import auto_bind from '../../common/auto_bind'
import moment from '../../common/moment'
import {events_difference} from '../../common/calendar'
import event_system from '../../common/event_system'
import {get_user_events} from '../../selectors/calendar'
import {get_user_missions_events} from '../../selectors/missions'
import {toggle_item_id} from '../../reducers/base'
const cx = classNames.bind(require('../../styles/input_calendar.scss'))
const chunks_count_in_hour = 4
const chunk_length_in_minutes = 60 / chunks_count_in_hour
const chunks_count_in_day = 24 * chunks_count_in_hour
const day_id = (day) => day.format("YYYY-MM-DD")
const get_periods = (chunk_set) => {
let periods = [], period_started, start = null
let add_period = (end) => periods.push({start, end})
range(chunks_count_in_day).forEach((chunk) => {
if (chunk_set[chunk]) {
if (!period_started) {
start = chunk
period_started = true
}
} else {
if (period_started) {
add_period(chunk - 1)
period_started = false
}
}
})
if (period_started) {
add_period(chunks_count_in_day - 1)
}
return periods
}
const get_start_end = (day, start_id, end_id) => {
let start_of_day = moment(day)
let start = start_id, end = end_id
if (start > end) {
start = end
end = start_id
}
return {
start: moment(start_of_day).add(start * chunk_length_in_minutes, 'minutes'),
end: moment(start_of_day).add((end + 1) * chunk_length_in_minutes, 'minutes'),
}
}
class AgencyEditUserCalendarStatic extends Component {
constructor(props) {
super(props)
auto_bind(this)
let active = {}
props.agency_events.forEach((event) => {
let current = moment(event.start)
while (current.isBefore(event.end)) {
let id = day_id(current)
if (!active[id]) {
active[id] = {}
}
active[id][(60 * current.hour() + current.minutes()) / chunk_length_in_minutes] = true
current.add(chunk_length_in_minutes, 'minutes')
}
})
this.state = {
active,
mode: "month",
week: 0,
}
}
componentDidMount() {
event_system.register('dismiss_popover', this.update_user_events)
}
componentDidUpdate() {
if (this.should_scroll_to_week) {
this.calendar.scroll_to_week(this.should_scroll_to_week)
this.should_scroll_to_week = false
}
}
on_day_click(date) {
let id = day_id(date)
if (this.is_full_day(id)) {
this.setState({
active: {
...this.state.active,
[id]: this.empty_day(),
}
})
} else if (this.is_empty_day(id)) {
this.setState({
active: {
...this.state.active,
[id]: this.full_day(),
},
})
} else {
this.on_navigate_to_week(date.endOf('week').diff(moment(), 'week'))
}
}
full_day() {
return set_from_array(range(chunks_count_in_day))
}
empty_day() {
return false
}
is_empty_day(id) {
let day = this.state.active[id]
return !day || empty_set(day)
}
is_full_day(id) {
let day = this.state.active[id]
return day && for_all(range(chunks_count_in_day), (chunk) => this.state.active[id][chunk])
}
on_create_days_period(start, end, paint = true) {
let day_ids = [], current = moment(start), condition = paint ? this.is_empty_day : this.is_full_day
while (current.isBefore(end)) {
let id = day_id(current)
if (condition(id)) {
day_ids.push(id)
}
current.add(1, 'day')
}
this.setState({active: {
...this.state.active,
...map_hash(set_from_array(day_ids), paint ? this.full_day : this.empty_day),
}})
}
on_create_chunks_period(date, chunks, paint = true) {
let id = day_id(date)
this.setState(({active}) => ({active: {
...active,
[id]: {
...(active[id] || {}),
...create_hash(chunks, () => paint)
},
}}))
}
get_day_events() {
return array_from_set(this.state.active).filter(this.is_full_day).map((day) => ({
start: moment(day),
end: moment(day).endOf('day'),
full_days: true,
}))
}
get_hour_events() {
let partially_busy_days = array_from_set(this.state.active).filter((id) => !this.is_full_day(id) && !this.is_empty_day(id))
let events = []
let add_events = (day, active_chunks) => {
get_periods(active_chunks).forEach(({start, end}) => {
events.push({
...get_start_end(day, start, end),
full_days: false,
})
})
}
partially_busy_days.forEach((day) => add_events(
day,
this.state.active[day]
))
return events
}
update_user_events() {
this.props.update_user_events([...this.get_day_events(), ...this.get_hour_events()])
}
on_navigate_to_week(week) {
this.setState({week, mode: "week"})
}
on_navigate_to_month() {
this.setState({mode: "month"})
this.should_scroll_to_week = this.state.week
}
on_next_week() {
this.setState({week: this.state.week + 1})
}
on_previous_week() {
this.setState({week: this.state.week - 1})
}
get_start_of_week() {
return moment().startOf('week').add(this.state.week, 'weeks')
}
render() {
const {user_events, mission_events} = this.props
const events = {
[color('important', 'light')]: user_events,
[color('primary')]: mission_events,
}
switch (this.state.mode) {
case "week":
let start_of_week = this.get_start_of_week()
return (
<div className={cx('input-calendar', 'input-calendar_week')}>
<div className={cx('input-calendar__nav')}>
<Button
className={cx('input-calendar__nav-back')}
back
onClick={this.on_navigate_to_month}
>
Retour vue {start_of_week.format(`MMMM${moment().isSame(start_of_week, 'year') ? '' : ' YYYY'}`)}
</Button>
<div className={cx('input-calendar__nav-weeks')}>
<div onClick={this.on_previous_week} className={cx('input-calendar__nav-weeks-control')}>
<MdKeyboardArrowLeft />
</div>
<div className={cx('input-calendar__nav-weeks-current')}>
Semaine {start_of_week.week()}
</div>
<div onClick={this.on_next_week} className={cx('input-calendar__nav-weeks-control')}>
<MdKeyboardArrowRight />
</div>
</div>
</div>
<TimePickers
events={events}
days={range(7).map((day) => {
let date = moment(start_of_week).add(day, 'days')
return {
date,
chunks: this.state.active[day_id(date)] || {},
}
})}
drag_color={color('attention')}
full_day_color={color('important')}
flip={this.on_create_chunks_period}
/>
</div>
)
case "month":
return (
<div className={cx('input-calendar', 'input-calendar_month')}>
<Calendar
ref={(calendar) => this.calendar = calendar}
events={{
...events,
[color('attention')]: this.get_hour_events(),
[color('important')]: this.get_day_events(),
}}
on_click={this.on_day_click}
drag_color={color('important')}
on_drag_and_drop={this.on_create_days_period}
on_navigate_to_week={this.on_navigate_to_week}
/>
</div>
)
}
}
}
const AgencyEditUserCalendar = connect(
(state, {user_id}) => {
const {user: user_events, agency: agency_events} = get_user_events(user_id)(state)
const mission_events = get_user_missions_events(user_id)(state)
return {
user_events,
agency_events,
mission_events,
}
},
(dispatch, {user_id}) => {
return {
update_user_events(new_events) {
let state = store.getState()
const {agency: old_events} = get_user_events(user_id)(state)
let {remove, add} = events_difference(old_events, new_events)
request.put(`users/${user_id}/events`, {remove_events: (remove || []).map((event) => event.id), events: add})
},
}
}
)(AgencyEditUserCalendarStatic)
export default AgencyEditUserCalendar