UNPKG

instantjob-recruiter-client

Version:

a set of tools for creating an instantjob recruiter react client

456 lines (416 loc) 12.2 kB
import React, {Component} from 'react' import classNames from 'classnames/bind' import {MdDone, MdLock, MdCached, MdInsertDriveFile} from 'react-icons/lib/md' import Dropzone from 'react-dropzone' import FileDropzone from './file_dropzone' import TimeAgo from 'components/utils/time_ago' import ProgressIndicator from './progress_indicator' import {instant_job_users_fields} from '../common/constants' import event_system from '../common/event_system' import auto_bind from '../common/auto_bind' import { property_getter, flatten_array, hash_with_key, property_toggler, empty_set, empty_array, map_hash, filter_hash, array_from_hash, download_file, } from '../common/utilities' import {update_item, toggle_item_id} from '../reducers/base' const cx = classNames.bind(require('../styles/user_profile.scss')) const value_width = 200 function field_is_empty({category, values, comment, file, value}) { switch (category) { case 'comment': return !comment case 'non_exclusive': return empty_array(values.filter(property_getter('selected'))) case 'file': return !file case 'exclusive': return !value } } const add_empty_property_to_field = (field) => ({ ...field, empty: field_is_empty(field), }) class UserProfile extends Component { constructor(props) { super(props) this.state = { ...props.user, progress: 0, } this.render_agency = this.render_agency.bind(this) } componentDidMount() { setTimeout(() => this.setState({progress: 6}), 500) } render_agency(agency) { let fields = agency.fields.map(({id}) => this.agency_fields[id]).filter(Boolean) if (!empty_array(fields)) { return ( <Agency {...agency} fields={fields} key={agency.id} /> ) } else { return <div key={agency.id}></div> } } render() { let {agencies} = this.props let instant_job_fields = instant_job_users_fields.map((field) => ({ ...field, comment: this.state[field.id] || "", update: (comment) => { this.setState({[field.id]: comment}) return this.props.update_instant_job_field(field.id, comment) }, })).map(add_empty_property_to_field) this.agency_fields = hash_with_key( flatten_array(this.props.agencies.map(property_getter('fields'))) .map(add_empty_property_to_field) .filter(({category, values}) => category != 'non_exclusive' || !empty_array(values)) ) let progress_fields = [ ...instant_job_fields, ...array_from_hash(this.agency_fields), ].filter(({locked}) => !locked) return ( <div className={cx('user-profile')}> <div className={cx('user-profile__header')}> <div className={cx('user-profile__header-title')}> Votre profil </div> <ProgressIndicator completed={progress_fields.filter(({empty}) => !empty).length} total={progress_fields.length} text={`Complété à ##%`}/> </div> <Agency key={'instantjob'} title={"Informations InstantJob"} subtitle={"Ces informations seront partagées avec l'ensemble des employeurs avec qui vous avez choisi de travailler."} fields={instant_job_fields} /> {agencies.map(this.render_agency)} </div> ) } } UserProfile.defaultProps = { agencies: [], user: {}, update_field: () => new Promise((resolve, reject) => resolve()), update_instant_job_field: () => new Promise((resolve, reject) => resolve()), } class Agency extends Component { constructor(props) { super(props) this.render_field = this.render_field.bind(this) } render_field(field) { return ( <Field {...field} key={field.id} /> ) } render() { let {fields, title, name, subtitle} = this.props return ( <div className={cx('user-profile__agency')}> <div className={cx('user-profile__agency-header')}> <div className={cx('user-profile__agency-header-title')}> {name ? `Informations pour ${name}` : title} </div> <div className={cx('user-profile__agency-header-subtitle')}> {subtitle || "Ces informations sont demandées spécifiquement par cet employeur et ne sont pas partagées avec vos autrs employeurs."} </div> </div> {fields.map(this.render_field)} </div> ) } } export class Field extends Component { constructor(props) { super(props) this.state = { synchronisations: {}, synchronized: true, } this.idGenerator = 1, this.render_indicator = this.render_indicator.bind(this) this.render_content = this.render_content.bind(this) } render_indicator() { let state, icon let {locked, empty} = this.props let synchronized = empty_set(this.state.synchronisations) if (locked) { state = 'locked' icon = <MdLock /> } else if (!synchronized) { state = 'synchronizing' icon = <MdCached /> } else if (empty) { state = 'empty' } else { state = 'synchronized' icon = <MdDone /> } return ( <div className={cx('user-profile__field-header-indicator', `user-profile__field-header-indicator_${state}`)}> {icon} </div> ) } render_content() { let field = this.props let props = { ...field, update: (...input) => { let synchronisation_id = this.idGenerator++ let toggle_synchronisation = () => this.setState(toggle_item_id(this.state, "synchronisations", synchronisation_id)) toggle_synchronisation() field.update(...input) .then(toggle_synchronisation) } } switch (field.category) { case 'comment': return ( <CommentField {...props} /> ) case 'non_exclusive': return ( <NonExclusiveField {...props} /> ) case 'file': return ( <FileField {...props} /> ) case 'exclusive': return ( <ExclusiveField {...props} /> ) } } render() { let {name} = this.props return ( <div className={cx('user-profile__field')}> <div className={cx('user-profile__field-header')}> {name} {this.render_indicator()} </div> {this.render_content()} </div> ) } } class CommentField extends Component { constructor(props) { super(props) this.state = { comment: props.comment, focused: false, } this.onFocus = this.onFocus.bind(this) this.onChange = this.onChange.bind(this) this.onBlur = this.onBlur.bind(this) } onFocus() { this.setState({focused: true}) this.initial_comment = this.state.comment } onChange({target: {value: comment}}) { if (!this.state.focused && comment != this.state.comment) { this.props.update(comment) } this.setState({comment}) } onBlur() { this.setState({focused: false}) if (this.state.comment != this.initial_comment) { this.props.update(this.state.comment) } } componentWillReceiveProps({comment}) { if (comment != this.state.comment && !this.state.focused) { this.setState({comment}) } } render() { let {comment, focused} = this.state let className = cx('user-profile__field-input', {'user-profile__field-input_empty': !comment, 'user-profile__field-input_focused': focused}) if (this.props.locked) { return ( <div className={className}> {comment} </div> ) } else { return ( <input className={className} type="text" value={comment || ""} onFocus={this.onFocus} onChange={this.onChange} onBlur={this.onBlur} /> ) } } } CommentField.defaultProps = { comment: "", } class NonExclusiveField extends Component { constructor(props) { super(props) this.state = { width: 500, } this.update_width = this.update_width.bind(this) this.render_value = this.render_value.bind(this) this.toggle_value = this.toggle_value.bind(this) } componentDidMount() { this.update_width() this.cancel_update_width = event_system.register("window_resize", this.update_width) } componentWillUnmount() { this.cancel_update_width() } update_width() { let width = $(this.values_container).width() if (width != this.state.width) { this.setState({width}) } } toggle_value(index) { return () => { let {values, update} = this.props update([ ...values.slice(0, index), property_toggler('selected')(values[index]), ...values.slice(index + 1), ]) } } render_value(width) { return ({id, name, selected}, index) => ( <div onClick={this.toggle_value(index)} key={id} className={cx('user-profile__field-values-value', {'user-profile__field-values-value_selected': selected})} style={{width}} > <div className={cx('user-profile__field-values-value-outline')}> <div className={cx('user-profile__field-values-value-name')}> {name} </div> <div className={cx('user-profile__field-values-value-icon')}> {selected ? ( <MdDone /> ) : null} </div> </div> </div> ) } render() { let available_width = this.state.width let columns_number = Math.floor(available_width / value_width) || 1 let width = `${100 / columns_number}%` let {values} = this.props return ( <div ref={(values_container) => this.values_container = values_container} className={cx('user-profile__field-values')} > {values.map(this.render_value(width))} </div> ) } } class FileField extends Component { constructor(props) { super(props) auto_bind(this) } on_files_drop([file]) { this.props.update(file) } view() { let {file: {url, name}} = this.props download_file(url, name) } render() { let {file} = this.props if (file) { if (file.uploading) { return ( <ProgressIndicator small className={cx('user-profile__field-dropzone')} completed={file.uploading} total={100} text="##%"/> ) } else { let {updated_at, url} = file return ( <div className={cx('user-profile__field-file')}> <div onClick={this.view} className={cx('user-profile__field-file-preview')}> <MdInsertDriveFile /> </div> <div className={cx('user-profile__field-file-updated-at')}> Mis à jour <TimeAgo>{updated_at}</TimeAgo> </div> <Dropzone style={{}} onDrop={this.on_files_drop}> <div className={cx('user-profile__field-file-update')}> Choisir un autre fichier </div> </Dropzone> </div> ) } } else { return ( <div className={cx('user-profile__field-dropzone')}> <FileDropzone on_files_drop={this.on_files_drop}> Déposer un fichier </FileDropzone> </div> ) } } } class ExclusiveField extends Component { constructor(props) { super(props) auto_bind(this) } update(values) { const selected_value_id = this.get_selected_value_id() values.forEach((value) => { if (value.id == selected_value_id && !value.selected) { return this.props.update(null) } if (value.selected && value.id != selected_value_id) { return this.props.update(value) } }) return new Promise((resolve, reject) => resolve()) } get_selected_value_id() { const {value} = this.props const {id} = value || {} return id } render() { const {values} = this.props const selected_value_id = this.get_selected_value_id() return ( <NonExclusiveField values={values.map((value) => ({...value, selected: value.id == selected_value_id}))} update={this.update} /> ) } } export default UserProfile