instantjob-recruiter-client
Version:
a set of tools for creating an instantjob recruiter react client
443 lines (427 loc) • 16.9 kB
JSX
import {tolerant_selector} from 'selectors/base'
import {date_to_id} from 'selectors/calendar'
import {get_users} from 'selectors/users'
import {get_recruiters} from 'selectors/recruiters'
import {
get_fields, get_fields_by_mission, get_mission_fields_by_deal, get_fields_by_proposal,
get_mission_values_by_mission, get_values_by_field, get_fields_by_deal,
} from 'selectors/fields'
import moment from 'common/moment'
import {
make_memoized, array_from_hash, array_from_set, split_hash, filter_hash, map_hash,
group_hash_by_value, for_all, flatten_array, split_array, set_from_array, property_getter,
set_from_hash, array_sum, array_minimum, array_maximum, set_intersection, filter_set,
create_hash, exist,
} from 'common/utilities'
import {
action_update_mission, action_cancel_mission, action_cancel_deal,
action_send_mission, action_assign_mission, action_unassign_mission, action_duplicate_deal,
action_add_mission, action_customize_deal_fields, deal_has_custom_fields, action_export_users,
action_toggle_publish_mission, action_notify_updated_mission, action_cancel_notify_updated_mission,
action_update_mission_events,
} from 'common/missions'
import {action_create_workplace} from 'common/workplaces'
import {make_periods} from 'common/calendar'
const get_raw_deals = (state) => state.missions.deals
const get_raw_missions = (state) => state.missions.missions
const get_raw_events = (state) => state.missions.events
const get_raw_mission_users = (state) => state.missions.users
const get_raw_workplaces = (state) => state.missions.workplaces
const get_raw_users = (state) => state.users.users
const get_profile_id = (state) => state.profile.id
const get_raw_agency = (state) => state.profile.agency
const get_raw_values = (state) => state.fields.values
const get_raw_fields = (state) => state.fields.fields
export const get_missions = tolerant_selector(
[
get_raw_missions, get_raw_mission_users, get_raw_events, get_raw_workplaces,
get_users, get_fields, get_fields_by_mission, get_raw_agency,
],
(missions, mission_users, events, workplaces, users, fields, fields_by_mission, {workplace_shared_with_user}) => {
let day_events_by_mission = {}
let hour_events_by_mission = {}
let not_full_days = {}
let start = {}
let end = {}
array_from_hash(events).forEach((e) => {
if (!e.deleted_at) {
let events_by_mission
if (e.full_days) {
events_by_mission = day_events_by_mission
} else {
not_full_days[e.calenderable_id] = true
events_by_mission = hour_events_by_mission
}
events_by_mission[e.calenderable_id] = [
...(events_by_mission[e.calenderable_id] || []),
e
]
if (!start[e.calenderable_id] || moment(e.start).isBefore(start[e.calenderable_id])) {
start[e.calenderable_id] = e.start
}
if (!end[e.calenderable_id] || moment(e.end).isAfter(end[e.calenderable_id])) {
end[e.calenderable_id] = e.end
}
}
})
const allowed_users_statuses = set_from_array(["active", "mail", "pending"])
return map_hash(missions, (mission) => {
const {id, users_count, workplace_id, recruiter_id} = mission
const users_status = mission_users[id] || {}
const grouped_users_status = group_hash_by_value(
users_status,
['new', 'accepted', 'rejected', 'assigned', 'impossible']
)
grouped_users_status.available = Object.keys(users)
.filter((user_id) => !users_status[user_id])
.filter((user_id) => users[user_id] && allowed_users_statuses[users[user_id].status.status])
const status = mission_status({...mission, grouped_users_status})
const assigned_count = grouped_users_status.assigned.length
const accepted_count = grouped_users_status.accepted.length
const day_events = day_events_by_mission[id] || []
const hour_events = hour_events_by_mission[id] || []
const fields = array_from_hash(fields_by_mission[id] || {}).map((field) => {
const {shared_with_user, } = field
if (shared_with_user) {
return {
...field,
...create_hash([
"on_select_value",
"on_deselect_value",
"on_clear",
"on_submit_comment",
], (action) => (...args) => {
action_notify_updated_mission(mission.id)
return field[action](...args)
})
}
}
return field
})
mission = {
...mission,
status,
sent: status != 'draft',
day_events,
hour_events,
events: [...day_events, ...hour_events],
grouped_users_status,
users_status,
workplace: workplaces[workplace_id] || {},
full_days: !not_full_days[id],
start: start[id],
end: end[id],
assigned_count,
accepted_count,
empty_count: mission.users_count - assigned_count,
waiting_count: grouped_users_status.new.length,
fields,
}
return {
...mission,
update(new_mission) {
action_update_mission(mission, {id, ...new_mission})
},
cancel() {
action_cancel_mission(mission)
},
create_workplace(workplace_name) {
return action_create_workplace(workplace_name)
},
set_workplace(workplace_id) {
action_notify_updated_mission(mission.id)
action_update_mission(mission, {id, workplace_id})
},
update_events(events) {
action_notify_updated_mission(mission.id)
return action_update_mission_events(mission.id, events)
},
send(user_ids) {
return action_send_mission(mission, user_ids)
},
assign(user_ids) {
return action_assign_mission(mission, user_ids)
},
unassign(user_ids) {
return action_unassign_mission(mission, user_ids)
},
toggle_publish() {
return action_toggle_publish_mission(mission)
},
cancel_update_notification() {
return action_cancel_notify_updated_mission(mission)
},
}
})
}
)
function mission_status({grouped_users_status, users_count, published}) {
if (grouped_users_status.assigned.length >= users_count) {
return "assigned"
} else if (for_all(["new", "rejected", "assigned", "accepted"], (status) => grouped_users_status[status].length == 0) && !published) {
return "draft"
} else if (grouped_users_status.new.length + grouped_users_status.accepted.length + grouped_users_status.assigned.length < users_count && !published) {
return "pending_selection"
} else if (grouped_users_status.accepted.length > 0) {
return "pending_assignement"
} else {
return "sent"
}
}
const make_mission_periods = (mission) => {
return make_periods(mission.events).map((period) => {
return ({
...period,
mission_start: mission.start,
mission_end: mission.end,
mission_status: mission.status,
mission_workplace: mission.workplace,
mission_id: mission.id,
})
})
}
export const get_user_missions = make_memoized((user_id) => tolerant_selector(
[get_mission_proposals],
(missions) => {
let user_missions = filter_hash(missions, (mission) => mission.users_status[user_id])
return array_from_hash(user_missions).map((mission) => {
return {
...mission,
user_status: mission.users_status[user_id],
periods: make_mission_periods(mission), // deprecated, do not use
}
})
.filter(({user_status}) => set_from_array(["new", "accepted", "rejected", "assigned"])[user_status])
}
))
export const get_user_missions_events = make_memoized((user_id) => tolerant_selector(
[get_user_missions(user_id)],
(missions) => flatten_array(missions.map(property_getter('events')))
))
export const get_mission_users = make_memoized((mission_id, status) => tolerant_selector(
[get_missions, get_users, get_mission_values_by_mission, get_values_by_field, get_raw_values, get_raw_fields],
(missions, users, mission_values_by_mission, values_by_field, values, fields) => {
let {grouped_users_status} = missions[mission_id] || {}
if (grouped_users_status) {
let user_ids_set = set_from_array(grouped_users_status[status])
if (status == "available") {
const accepted_values = {}
mission_values_by_mission[mission_id].forEach((value_id) => {
const {field_id} = values[value_id]
const {for_entity, category, recycled} = fields[field_id]
if (for_entity == 'User' && recycled) {
if (category == 'non_exclusive') {
user_ids_set = set_intersection(user_ids_set, values[value_id].entity_ids)
} else if (category == 'exclusive') {
accepted_values[field_id] = {
...(accepted_values[field_id] || {}),
[value_id]: true,
}
}
}
})
array_from_hash(fields).forEach(({id, category, for_entity, recycled, value_id_for}) => {
if (for_entity == 'User' && recycled && category == 'exclusive') {
const values = accepted_values[id]
if (values) {
user_ids_set = filter_set(user_ids_set, (user_id) => {
return values[value_id_for[user_id]]
})
}
}
})
}
return filter_hash(users, ({id, status: {status}}) => user_ids_set[id] && status != 'applied')
} else {
return {}
}
}
))
export const get_initially_selected_recruiters = tolerant_selector(
[get_recruiters, get_profile_id],
(recruiters, profile_id) => {
if (profile_id) {
const {team_id, administrator} = recruiters[profile_id] || {}
if (administrator) {
return []
} else if (team_id) {
return array_from_hash(recruiters).filter(({team_id: id}) => id == team_id).map(property_getter('id'))
} else {
return [profile_id]
}
} else {
return []
}
}
)
export const get_deals = tolerant_selector(
[get_raw_deals, get_missions, get_raw_workplaces, get_recruiters, get_mission_fields_by_deal, get_fields_by_deal],
(deals, all_missions, workplaces, recruiters, mission_fields_by_deal, fields_by_deal) => {
const template_mission_ids = set_from_hash(map_hash(deals, property_getter('template_mission_id')))
const missions_by_deal = split_array(
array_from_hash(filter_hash(all_missions, ({id, deleted_at}) => !deleted_at && !template_mission_ids[id])),
property_getter('deal_id'),
Object.keys(deals),
)
return map_hash(
deals,
(deal) => {
const {
id, template_mission_id, custom_field_ids, has_custom_events, has_custom_workplace,
recruiter_id,
} = deal
function filter_fields({fields, ...mission}, keep_custom = true) {
return {
...mission,
fields: (fields || []).filter(({id, category}) =>
Boolean(keep_custom) == Boolean(custom_field_ids[id] || category == 'computed')
),
}
}
function add_deals_fields({fields, ...mission}) {
return {
...mission,
fields: [...fields, ...array_from_hash(fields_by_deal[id] || {})]
}
}
const template_mission = add_deals_fields(filter_fields(all_missions[template_mission_id] || {}, false))
const missions = missions_by_deal[id]
.map((mission) => filter_fields(mission, true))
.filter(({deleted_at}) => !deleted_at)
const counts = {};
(new Array('users_count', 'accepted_count', 'assigned_count', 'empty_count', 'waiting_count')).forEach((count) => {
counts[count] = array_sum(missions.map(property_getter(count, 0)))
})
const start = has_custom_events ? array_minimum(missions.map(property_getter('start')), (a, b) => moment(a).isBefore(b)) : template_mission.start
const end = has_custom_events ? array_maximum(missions.map(property_getter('end')), (a, b) => moment(b).isBefore(b)) : template_mission.end
const events = has_custom_events ? flatten_array(missions.map(property_getter('events'))) : template_mission.events
const workplace_ids = has_custom_workplace ? missions.map(property_getter('workplace_id')) : [template_mission.workplace_id]
const own_workplaces = workplace_ids.map((workplace_id) => workplaces[workplace_id] || {})
deal = {
...deal,
...counts,
missions,
sent: exist(missions, property_getter('sent')),
template_mission,
status: deal_status(missions),
workplace_ids,
workplaces: own_workplaces,
workplace: own_workplaces[0],
recruiter: recruiters[recruiter_id] || {},
start,
end,
events,
fields: mission_fields_by_deal[id],
has_custom_fields: deal_has_custom_fields(deal),
}
return {
...deal,
cancel() {
action_cancel_deal(deal)
},
duplicate() {
action_duplicate_deal(deal)
},
add_mission() {
action_add_mission(deal)
},
customize_fields() {
action_customize_deal_fields(deal)
},
export_users() {
return action_export_users(deal)
}
}
}
)
}
)
export function deal_status(missions) {
const statuses = split_array(missions, property_getter('status'), ['pending_selection', 'pending_assignement', 'assigned', 'sent', 'draft'])
if (statuses['assigned'].length == missions.length) {
return 'assigned'
} else if (statuses['pending_selection'].length) {
return 'pending_selection'
} else if (statuses['pending_assignement'].length) {
return 'pending_assignement'
} else if (statuses['draft'].length) {
return 'draft'
} else {
return 'sent'
}
}
export const get_displayed_deals = tolerant_selector(
[get_deals, get_recruiters],
(deals, recruiters) => {
let default_deal_ids = set_from_array(array_from_hash(recruiters).map(property_getter('default_deal_id')))
return array_from_hash(deals).filter(({id, deleted_at}) => !deleted_at && !default_deal_ids[id])
}
)
export const get_default_deal = tolerant_selector(
[get_profile_id, get_recruiters, get_deals],
(id, recruiters, deals) => {
const {default_deal_id} = recruiters[id] || {}
return deals[default_deal_id]
}
)
export const get_mission_proposals = tolerant_selector(
[get_missions, get_deals, get_raw_agency, get_recruiters],
(missions, deals, agency, recruiters) => map_hash(missions, (mission) => {
const deal = deals[mission.deal_id]
const {
has_custom_events, has_custom_workplace, template_mission_id, custom_field_ids,
recruiter_id,
} = deal || {custom_field_ids: {}}
const template_mission = missions[template_mission_id] || {}
let status = mission.status
let sent = mission.sent
if (template_mission_id == mission.id) {
status = deal.status
sent = deal.sent
}
const {fields, workplace} = template_mission
const {workplace_shared_with_user} = agency
function get_event_properties({start, end, events}) {
return {start, end, events}
}
const real_workplace = has_custom_workplace ? mission.workplace : workplace
return {
...mission,
deal,
status,
sent,
real_workplace,
workplace: workplace_shared_with_user ? real_workplace : null,
agency,
recruiter: recruiters[recruiter_id] || {},
...get_event_properties(has_custom_events ? mission : template_mission),
fields: (fields || []).filter(property_getter('shared_with_user')).map((field, index) => {
if (custom_field_ids[field.id]) {
return mission.fields[index]
} else {
return field
}
})
}
})
)
export const get_user_deals = make_memoized((user_id) => tolerant_selector(
[get_user_missions(user_id), get_deals],
(missions, deals) => {
const deal_ids = {}
missions.forEach(({deal_id}) => deal_ids[deal_id] = true)
return array_from_hash(deals).filter(({id}) => deal_ids[id])
}
))
export const get_proposals = make_memoized((mission_id) => tolerant_selector(
[get_missions, get_users, get_fields_by_proposal],
(missions, users, fields_by_proposal) => {
const {assigned_proposals} = missions[mission_id] || {assigned_proposals: []}
return assigned_proposals.map(({user_id, ...proposal}) => ({
...proposal,
mission_id,
user: users[user_id] || {},
fields: array_from_hash(fields_by_proposal[proposal.id]) || [],
}))
}
))