instantjob-recruiter-client
Version:
a set of tools for creating an instantjob recruiter react client
367 lines (346 loc) • 10.7 kB
JSX
import React, {Component} from 'react'
import styled from 'styled-components'
import {connect} from 'react-redux'
import {MdFlag, MdPhone, MdEmail, MdPerson, MdBusiness} from 'react-icons/lib/md'
import {browserHistory} from 'react-router'
import LastConnection from 'components/icons/last_connection'
import ProfileImage from 'components/profile_image'
import TimeAgo from 'components/utils/time_ago'
import Percentage from 'components/utils/percentage'
import Tag from 'components/utils/tag'
import FilterableList from 'components/filterable_list/filterable_list'
import {get_filter_on_click} from 'components/filterable_list/make_filter_maker_from_field'
import {FilterContent, FilterInput} from 'components/filterable_list/shared'
import persistent_state from 'common/persistent_state'
import {make_user_statuses, new_users_fuse} from 'common/users'
import auto_bind from 'common/auto_bind'
import {link, color} from 'common/styles'
import {
property_getter, distance, set_from_array, set_count, set_intersection, array_from_set,
} from 'common/utilities'
import moment from 'common/moment'
import {get_user_workplaces_count} from 'selectors/user_missions'
import {get_workplaces} from 'selectors/workplaces'
function make_sort_by_distance(setState) {
return (place) => get_filter_on_click(`place_${place.latitude}_${place.longitude}`, setState, (item) => {
const user_distance = distance(item, place)
if (user_distance === null) {
return 1e9
} else {
return user_distance
}
})
}
class UsersList extends Component {
constructor(props) {
super(props)
this.state = persistent_state.get(props.persistent_state_key, {
...FilterableList.initial_state,
exclusive_filters: {
status: {
'mail': true,
'active': true,
},
},
order_by({last_connection}) {
return last_connection ? -moment(last_connection).valueOf() : 0
},
workplace: props.workplace,
radius_filter: null,
})
auto_bind(this)
}
static defaultProps = {
users: [],
fields: [],
clickable: true,
innerRef() {},
}
componentDidMount() {
this.props.innerRef(this)
}
componentWillUnmount() {
persistent_state.store(this.props.persistent_state_key, this.state)
}
set_state(arg) {
this.setState(arg)
}
order_by_distance() {
make_sort_by_distance(this.set_state)(this.state.workplace)()
}
get_selected_count(selected_user_ids) {
return set_count(set_intersection(selected_user_ids, set_from_array(this.props.users.map(property_getter('id')))))
}
get_status_filter_maker() {
return FilterableList.make_filter_maker_from_field({
id: 'status',
name: 'Statut',
category: 'exclusive',
value_ids: ['active', 'mail', 'rejected', 'inactive', 'blacklisted', 'invited', 'new'],
get_value_props(status, selected = false) {
const {name, icon} = make_user_statuses(selected ? 'white' : color('black', 'light'))[status]
return {
id: status,
name: (
<Tag icon={icon}>
{name}
</Tag>
),
}
},
get_item_values({status: {status}}) {
return [status]
},
})
}
get_missions_count_sort_maker() {
return FilterableList.make_sort_maker({
id: 'missions_count',
name: (
<Tag icon={<MdFlag />}>
Missions réalisées
</Tag>
),
compare_with: ({missions_count}) => -missions_count,
})
}
get_profile_rate_sort_maker() {
return FilterableList.make_sort_maker({
id: 'profile_rate',
name: (
<Tag icon={<MdPerson />}>
Remplissage du profil
</Tag>
),
compare_with: ({profile_rate}) => -profile_rate,
})
}
get_place_filter_maker() {
return {
keep_item(item, {workplace, radius_filter}) {
return !workplace || !radius_filter || distance(item, workplace) < radius_filter * 1000
},
make_filter_props({setState, state: {workplace, radius_filter}}) {
const sort_by_distance = make_sort_by_distance(setState)
return {
id: 'place',
name: 'Distance',
content: (
<PlaceFilter
place={workplace}
radius_filter={radius_filter}
setState={setState}
sort_by_distance={sort_by_distance}
/>
),
onClick: workplace && sort_by_distance(workplace)
}
}
}
}
get_workplace_filter_maker() {
const {workplaces, user_workplaces_count} = this.props
return FilterableList.make_filter_maker_from_field({
id: 'workplaces',
name: 'Lieux où le candidat a travaillé',
category: 'exclusive',
tolerant: true,
value_ids: Object.keys(workplaces),
get_value_props(workplace_id) {
return workplaces[workplace_id] || {}
},
get_item_values({id}) {
return array_from_set(user_workplaces_count[id])
},
})
}
get_filters_and_users() {
const {users, fields} = this.props
const name_filter_maker = FilterableList.make_name_filter_maker('full_name')
const filter_makers = [
this.get_status_filter_maker(),
name_filter_maker,
this.get_missions_count_sort_maker(),
this.get_profile_rate_sort_maker(),
this.get_place_filter_maker(),
this.get_workplace_filter_maker(),
...fields.map(FilterableList.make_filter_maker_from_field).filter(Boolean)
]
return {
filters: FilterableList.get_filters(filter_makers, users, this.state, this.set_state),
users: FilterableList.get_filtered_items(
filter_makers,
users,
this.state,
),
name_filter: name_filter_maker.make_filter(this.set_state, users, new_users_fuse),
}
}
render_user(user) {
const {clickable, user_workplaces_count} = this.props
const {id, first_name, last_name, phone_number, postal_code, missions_count, profile_rate, last_connection, email} = user
const {workplace} = this.state
const user_distance = workplace && distance(user, workplace)
return (
<User onClick={clickable && !user.not_clickable ? () => browserHistory.push(`/users/${id}`) : null}>
<ProfileImage large {...user} />
<Information>
<Top>
<Name>
{last_name} {first_name}
</Name>
<Statistics>
{[
workplace ? {value: user_workplaces_count[id][workplace.id] || 0, icon: <MdBusiness />} : null,
{value: missions_count, icon: <MdFlag />},
{value: <Percentage>{profile_rate}</Percentage>, icon: <MdPerson />},
last_connection ? {value: <TimeAgo>{last_connection}</TimeAgo>, icon: <LastConnection />} : null,
].filter(Boolean).map(({value, icon}, index) => (
<Statistic key={index}>
{value}
<StatisticIcon>
{icon}
</StatisticIcon>
</Statistic>
))}
</Statistics>
</Top>
<Bottom>
<Contacts>
{[
{icon: <MdPhone />, value: phone_number},
{icon: <MdEmail />, value: email},
].map(({icon, value}, index) => value ? (
<Contact key={index}>
<ContactIcon>
{icon}
</ContactIcon>
{value}
</Contact>
) : null)}
</Contacts>
<Location>
{workplace && user_distance !== null ? `${(user_distance / 1000).toFixed(1)} km` : postal_code}
</Location>
</Bottom>
</Information>
</User>
)
}
render() {
const {secondary_actions, main_action, selectable} = this.props
const {filters, users, name_filter} = this.get_filters_and_users()
return (
<FilterableList
icon={MdPerson}
empty_text={"Aucun candidat"}
filters={filters}
name_filter={name_filter}
secondary_actions={secondary_actions}
main_action={main_action}
render_item={this.render_user}
get_selected_count={this.get_selected_count}
selectable={selectable}
>
{users}
</FilterableList>
)
}
}
const PlaceFilter = ({place, radius_filter, setState, sort_by_distance}) => {
function on_place_select({label: address, location: {lat: latitude, lng: longitude}}) {
const place = {address, latitude, longitude}
setState({place})
sort_by_distance(place)()
}
function on_clear_place_input() {
setState({place: null})
}
function on_radius_change(radius_filter) {
setState({radius_filter})
}
return (
<FilterContent>
<FilterInput
label="Trier en fonction de la distance à"
type='place'
on_place_select={on_place_select}
on_clear_place_input={on_clear_place_input}
initial_query={(place && place.address) || ""}
/>
<FilterInput
label="Filtrer les candidats au delà de (km)"
value={radius_filter || ""}
onChange={on_radius_change}
/>
</FilterContent>
)
}
export default connect(
(state) => ({
workplaces: get_workplaces(state),
user_workplaces_count: get_user_workplaces_count(state),
})
)(UsersList)
const User = styled.div`
flex: 1;
${({onClick}) => onClick ? link : ''}
display: flex;
align-items: center;
padding-right: 5px;
`
const Information = styled.div`
flex: 1;
display: flex;
align-items: stretch;
justify-content: space-between;
flex-direction: column;
margin-left: 10px;
height: 50px;
`
const Top = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`
const Name = styled.div`
font-size: 18px;
`
const Statistics = styled.div`
display: flex;
align-items: center;
`
const Statistic = styled.div`
display: flex;
align-items: center;
margin-left: 10px;
`
const StatisticIcon = styled.div`
margin-left: 3px;
font-size: 16px;
`
const Bottom = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`
const Contacts = styled.div`
display: flex;
align-items: center;
`
const Contact = styled.div`
display: flex;
margin-right: 10px;
`
const ContactIcon = styled.div`
margin-right: 5px;
`
const Location = styled.div``
const Status = styled.div`
display: flex;
align-items: center;
`
const StatusIcon = styled.div`
padding: 5px;
font-size: 16px;
`