UNPKG

instantjob-recruiter-client

Version:

a set of tools for creating an instantjob recruiter react client

1,070 lines (1,034 loc) 46.4 kB
import React, {Component} from 'react' import {connect} from 'react-redux' import classNames from 'classnames/bind' import { MdError, MdWarning, MdCheck, MdCheckBox, MdCheckBoxOutlineBlank, MdFileUpload, MdKeyboardArrowLeft, MdKeyboardArrowRight, } from 'react-icons/lib/md' import Menu from './menu' import Button from 'components/utils/button' import UserTable from './user_table' import FieldCategoriesSelector from './field_categories_selector' import { array_from_hash, array_from_set, set_from_array, human_plural, human_join, deep_merge, for_all, create_hash, hash_key_with_value, hash_with_key, map_hash, array_contains, group_hash_by_value, filter_hash, } from '../common/utilities' import request from '../common/request' import store from '../common/store' import {color} from '../common/constants' import { action_create_field, action_create_value, } from '../common/fields' import {action_invite_users, get_user_status, make_user_status} from '../common/users' import {alert_info, alert_success} from '../actions/display' import {update_users} from '../actions/users' import { update_user_comments, add_users_value, remove_users_value, update_exclusive_entity_value, clear_exclusive_entity_value, } from '../actions/fields' import {combined_actions} from 'actions/base' import {get_users} from '../selectors/users' let cx = classNames.bind(require('../styles/upload_results.scss')) const get_uploaded_users = (upload_users, props_users) => { return map_hash(upload_users, (user) => ({ ...user, status: user.existing ? get_user_status(props_users[user.existing]) : make_user_status('new'), })) } class UploadResultsStatic extends Component { constructor(props) { super(props) this.state = { active_field_type: 'summary', active_field: null, messages: { summary: [], invitations: [], instant_job_field: {}, agency_field: {}, }, invitations: true, importing: { instant_job_field: {}, agency_field: {}, }, imported: { instant_job_field: {}, agency_field: {}, }, statistics: 'old', } this.render_page = this.render_page.bind(this) this.send_invitations = this.send_invitations.bind(this) this.import_users = this.import_users.bind(this) this.import_field = this.import_field.bind(this) this.render_navigation = this.render_navigation.bind(this) } componentWillUnmount() { this.mounted = false } componentDidMount() { this.mounted = true let summary = [], invitations = [], instant_job_field = {}, agency_field = {} let { users, fields, values, instant_job_fields_present, new_users_count, old_users_count, duplicate_users_count, invalid_emails, } = this.props.upload_results if (new_users_count > 0) { this.setState({statistics: 'new'}) } let new_field_names = Object.keys(fields) .map((id) => fields[id]) .filter((field) => !field.existing) .map((field) => field.name) let total_fields_count = Object.keys(fields).length + array_from_set(instant_job_fields_present).length summary.new_fields = { new_field_names, total_fields_count, component: MessageFieldsCount, } if (duplicate_users_count > 0) { summary.duplicate_users = { duplicate_users_count, component: MessageDuplicateUsers, } } if (invalid_emails.length > 0) { summary.duplicate_users = { invalid_emails, component: MessageInvalidEmails, } } if (new_users_count == 0 && old_users_count == 0) { summary.empty_file = { component: MessageEmptyFile, } } if (new_users_count > 0) { invitations.new_users = { validated: false, all_users: Object.keys(users).length, new_users: new_users_count, component: MessageInviteUsers, onClick: () => this.setState(deep_merge(this.state, { messages: {invitations: {new_users: {validated: !this.state.messages.invitations.new_users.validated}}} })), } } let absent_fields = array_from_hash(filter_hash( group_hash_by_value(this.props.instant_job_fields), (labels) => for_all(labels, (label) => !instant_job_fields_present[label]) )).map((labels) => labels[0]) if (absent_fields.length > 0) { invitations.absent_fields = { validated: false, absent_fields, component: MessageAbsentFields, onClick: () => this.setState(deep_merge(this.state, { messages: {invitations: {absent_fields: {validated: !this.state.messages.invitations.absent_fields.validated}}} })), } } array_from_set(instant_job_fields_present).forEach((field_label) => { let field_name = this.props.instant_job_fields[field_label] if (new_users_count > 0) { instant_job_field[field_name] = deep_merge(instant_job_field[field_name], {new: {valid: { component: MessageInstantJobFieldNew, informational: true, }}}) } if (old_users_count > 0) { let conflicting_users = array_from_hash(users).filter((user) => { if (user.existing) { return (user[field_name] || "") != (this.props.users[user.existing][field_name] || "") } else { return false } }) let conflicting_users_with_app_count = conflicting_users.filter((user) => this.props.users[user.existing].app_name).length let conflicting_users_without_app_count = conflicting_users.length - conflicting_users_with_app_count if (conflicting_users.length > 0) { if (conflicting_users_without_app_count > 0) { instant_job_field[field_name] = deep_merge(instant_job_field[field_name], {old: {conflict: { component: MessageFieldConflict, conflicting_users_count: conflicting_users_without_app_count, selected: null, exists_new_user: false, on_select: (selected) => this.setState(deep_merge(this.state, { messages: {instant_job_field: {[field_name]: {old: {conflict: {selected}}}}} })), }}}) } if (conflicting_users_with_app_count > 0) { instant_job_field[field_name] = deep_merge(instant_job_field[field_name], {old: {conflict_with_app: { component: MessageInstantJobAppUserConflict, conflicting_users_count: conflicting_users_with_app_count, informational: true, }}}) } } else { instant_job_field[field_name] = deep_merge(instant_job_field[field_name], {old: {identical: { component: MessageFieldIdentical, informational: true, }}}) } } }) array_from_hash(fields).forEach((field) => { if (field.existing) { let conflicting_users_count, new_values_old_users const field_values = array_from_set(field.values).map((id) => values[id]) switch (field.category) { case 'non_exclusive': if (new_users_count > 0) { let new_values_new_users = field_values.filter((value) => !value.existing && value.created_by_new_users).map((value) => value.name) if (new_values_new_users.length > 0) { agency_field[field.id] = deep_merge(agency_field[field.id], {new: {new_values_new_users: { component: MessageNewValuesNewUsers, new_field_values: new_values_new_users, new_users_count, validated: false, onClick: () => this.setState(deep_merge(this.state, { messages: {agency_field: {[field.id]: {new: {new_values_new_users: {validated: !this.state.messages.agency_field[field.id].new.new_values_new_users.validated}}}}} })), }}}) } else { agency_field[field.id] = deep_merge(agency_field[field.id], {new: {ready: { component: MessageAgencyFieldReady, validated: true, }}}) } } conflicting_users_count = array_from_hash(users).filter((user) => ( user.existing && !for_all( field_values, (value) => !((!value.existing && value.entity_ids[user.id]) || value.mismatching_users[user.id]) ) )).length new_values_old_users = field_values.filter((value) => value.created_by_old_users).map((value) => value.name) break case 'exclusive': if (new_users_count > 0) { let new_values_new_users = field_values.filter((value) => !value.existing && value.created_by_new_users).map((value) => value.name) if (new_values_new_users.length > 0) { agency_field[field.id] = deep_merge(agency_field[field.id], {new: {new_values_new_users: { component: MessageNewValuesNewUsers, new_field_values: new_values_new_users, new_users_count, validated: false, onClick: () => this.setState(deep_merge(this.state, { messages: {agency_field: {[field.id]: {new: {new_values_new_users: {validated: !this.state.messages.agency_field[field.id].new.new_values_new_users.validated}}}}} })), }}}) } else { agency_field[field.id] = deep_merge(agency_field[field.id], {new: {ready: { component: MessageAgencyFieldReady, validated: true, }}}) } } conflicting_users_count = array_from_hash(users) .filter((user) => user.existing && field.mismatching_user_ids[user.id]) .length new_values_old_users = field_values .filter((value) => value.created_by_old_users) .map((value) => value.name) break case "comment": if (new_users_count > 0) { agency_field[field.id] = deep_merge(agency_field[field.id], {new: {ready: { component: MessageAgencyFieldReady, validated: true, }}}) } conflicting_users_count = array_from_hash(users).filter((user) => field.conflicting_comments[user.id]).length new_values_old_users = [] break default: } if (old_users_count > 0) { if (conflicting_users_count > 0) { agency_field[field.id] = deep_merge(agency_field[field.id], {old: {conflict: { component: MessageFieldConflict, conflicting_users_count, new_field_values: new_values_old_users, selected: null, exists_new_user: new_users_count > 0, on_select: (selected) => this.setState(deep_merge(this.state, { messages: {agency_field: {[field.id]: {old: {conflict: {selected}}}}} })), }}}) } else { agency_field[field.id] = deep_merge(agency_field[field.id], {old: {identical: { component: MessageFieldIdentical, informational: true, }}}) } } } else { agency_field[field.id] = deep_merge(agency_field[field.id], {common: {new_field: { component: MessageNewField, selected: null, on_select: (category) => { this.props.upload_results.fields = { ...this.props.upload_results.fields, [field.id]: { ...field, category } } this.setState(deep_merge(this.state, { messages: {agency_field: {[field.id]: {common: {new_field: {selected: category}}}}} })) }, }}}) } }) this.setState({messages: {summary, invitations, instant_job_field, agency_field}}) } send_invitations() { this.setState(deep_merge(this.state, {importing: {invitations: true}})) let users = array_from_hash(this.props.upload_results.users).filter((user) => !user.existing) action_invite_users(users) .then(() => this.setState(deep_merge(this.state, {imported: {invitations: true}}))) } import_users(field_name) { let users = array_from_hash(this.props.upload_results.users) .filter((user) => user.existing && !this.props.users[user.existing].app_name) .filter((user) => this.props.users[user.existing][field_name] != user[field_name]) .map((user) => ({...this.props.users[user.existing], [field_name]: user[field_name]})) store.dispatch(update_users(users.filter((user) => user.user_id).map((user) => ({...user, id: user.user_id})))) return request.asynchronous(`users/agency_update`, {users: users.map(({first_name, last_name, email, postal_code, phone_number}) => ({first_name, last_name, email, postal_code, phone_number}))}) .then((users) => store.dispatch(update_users(users))) } import_field(imported_field_id, old_users_import_mode) { let { users, fields, values, } = this.props.upload_results let field = fields[imported_field_id] let users_to_import = array_from_hash(users) if (old_users_import_mode == 'ignore') { users_to_import = users_to_import.filter((user) => !user.existing) } let existing_users = hash_with_key(array_from_hash(this.props.users), 'email') users = map_hash(users, (user) => ({...user, existing: existing_users[user.email]})) let should_import_user = set_from_array(users_to_import.map((user) => users[user.id].existing.id)) let imported_users = hash_with_key(array_from_hash(users), 'email') return (new Promise((resolve, reject) => { if (field.existing) { resolve(field.existing) } else { action_create_field(field) .then((id) => resolve(id)) } })).then((field_id) => { const actions = [] switch (field.category) { case "non_exclusive": return Promise.all(array_from_set(field.values).map((id) => { let imported_value = values[id] return new Promise((resolve, reject) => { if (imported_value.existing) { resolve(imported_value) } else { action_create_value(field_id, imported_value.name) .then((id) => resolve({...imported_value, existing: id})) } }) })) .then((imported_values) => request.asynchronous("users/update_values", {values: imported_values.map((imported_value) => { let value = this.props.values[imported_value.existing] let result = { id: value.id, added: array_from_set(imported_value.entity_ids) .map((id) => users[id].existing.id) .filter((user_id) => !value.entity_ids[user_id] && should_import_user[user_id]), removed: array_from_set(value.entity_ids).filter((user_id) => { let imported_user = imported_users[this.props.users[user_id].email] return imported_user && !imported_value.entity_ids[imported_user.id] && should_import_user[user_id] }) } actions.push(add_users_value(result.added, result.id)) actions.push(remove_users_value(result.removed, result.id)) return result })})) .then((result) => { store.dispatch(combined_actions(actions)) return result }) case "exclusive": return Promise.all(array_from_set(field.values).map((id) => { let imported_value = values[id] return new Promise((resolve, reject) => { if (imported_value.existing) { resolve(imported_value) } else { action_create_value(field_id, imported_value.name) .then((id) => resolve({...imported_value, existing: id})) } }) })) .then((imported_values) => request.asynchronous("users/update_values", {values: imported_values.map((imported_value) => { const value = this.props.values[imported_value.existing] const {value_id_for} = this.props.fields[value.field_id] || {} const result = { id: value.id, added: Object.keys(field.value_id_for) .filter( (user_id) => field.value_id_for[user_id] == imported_value.id && value_id_for[users[user_id].existing.id] != field.value_id_for[user_id] ) .map((id) => users[id].existing.id) .filter((user_id) => should_import_user[user_id]), removed: Object.keys(value_id_for) .filter((user_id) => { if (value_id_for[user_id] != value.id) { return false } const imported_user = imported_users[this.props.users[user_id].email] return imported_user && !field.value_id_for[imported_user.id] && should_import_user[user_id] }) } result.added.forEach((user_id) => { actions.push(update_exclusive_entity_value(user_id, value.id)) }) result.removed.forEach((user_id) => { actions.push(clear_exclusive_entity_value(user_id, value.id)) }) return result })})) .then((result) => { store.dispatch(combined_actions(actions)) return result }) case "comment": let query = array_from_hash(users).filter((user) => ( should_import_user[user.existing.id] && ( field.conflicting_comments[user.id] || ( !field.existing && field.comments[user.id] != "" ) ) )).map((user) => ({id: user.existing.id, comment: field.comments[user.id]})) store.dispatch(update_user_comments(field_id, map_hash(hash_with_key(query), (user) => user.comment))) return request.asynchronous("users/update_comments", {users: query, field: {id: field_id}}) default: } }) } render_page() { let exists_new_user = this.props.upload_results.new_users_count > 0 let exists_old_user = this.props.upload_results.old_users_count > 0 switch (this.state.active_field_type) { case 'summary': case 'invitations': let messages = array_from_hash(this.state.messages[this.state.active_field_type]) let message_wrappers = (messages || []).map(({component: MessageWrapper, ...props}, index) => ( <MessageWrapper key={index} {...props} imported={this.state.imported.invitations} /> )) if (this.state.active_field_type == 'summary') { return ( <div className={cx('upload-results__content-container')}> <div className={cx('upload-results__content-title')}> Aperçu </div> {message_wrappers} {exists_old_user || exists_new_user ? ( <UserTable {...this.props.upload_results} editable={false} place_filterable={false} users={get_uploaded_users(this.props.upload_results.users, this.props.users)}/> ) : null} </div> ) } else { return ( <div className={cx('upload-results__content-container')}> <div className={cx('upload-results__content-title')}> Invitations </div> <div className={cx('upload-results__messages')}> {message_wrappers} </div> <div className={cx('upload-results__upload-button')}> {this.state.imported.invitations ? ( <Button done> {human_plural(this.props.upload_results.new_users_count, `L'invitation a été envoyée`, `Les invitations ont été envoyées`)} </Button> ) : (this.state.importing.invitations ? ( <Button loading> Importation en cours ... </Button> ) : ( <Button disabled={!for_all(messages, (message) => message.informational || message.validated || message.selected)} onClick={this.send_invitations} > {human_plural(this.props.upload_results.new_users_count, `Envoyer l'invitation`, `Envoyer les invitations`)} </Button> ))} </div> </div> ) } case 'instant_job_field': case 'agency_field': let imported = this.state.imported[this.state.active_field_type][this.state.active_field] let new_field = this.state.active_field_type == 'agency_field' && !this.props.upload_results.fields[this.state.active_field].existing let common_messages = array_from_hash((this.state.messages[this.state.active_field_type][this.state.active_field] || {}).common) let common_message_wrappers = common_messages.map(({component: MessageWrapper, ...props}, index) => ( <MessageWrapper key={index} {...props} imported={imported} /> )) let new_users_messages = array_from_hash((this.state.messages[this.state.active_field_type][this.state.active_field] || {}).new) let new_users_message_wrappers = new_users_messages.map(({component: MessageWrapper, ...props}, index) => ( <MessageWrapper key={index} {...props} imported={imported} /> )) let old_users_messages = array_from_hash((this.state.messages[this.state.active_field_type][this.state.active_field] || {}).old) let old_users_message_wrappers = old_users_messages.map(({component: MessageWrapper, ...props}, index) => ( <MessageWrapper key={index} {...props} imported={imported} /> )) let statistics let switcher = exists_new_user && exists_old_user ? ( <div className={cx('upload-results__statistics-switcher')}> {["new", "old"].map((option) => ( <div key={option} className={cx('upload-results__statistics-switcher-option', {'upload-results__statistics-switcher-option_selected': this.state.statistics == option})} onClick={() => this.setState({statistics: option})} > {{"new": "Nouveaux candidats", "old": "Anciens candidats"}[option]} </div> ))} </div> ) : null const filter_user = (user) => this.state.statistics == (user.existing ? "old" : "new") let field if (this.state.active_field_type == 'instant_job_field') { field = {name: hash_key_with_value(this.props.instant_job_fields, this.state.active_field)} let all_users = array_from_hash(this.props.upload_results.users).filter(filter_user) let filled_users_count = all_users.filter((user) => user[this.state.active_field]).length statistics = ( <div className={cx('upload-results__statistics')}> {switcher} <div className={cx('upload-results__content-title')}> Statistiques </div> <Statistics> {{ "Renseigné": filled_users_count, "Vide": all_users.length - filled_users_count, }} </Statistics> </div> ) } else { field = this.props.upload_results.fields[this.state.active_field] switch (field.category) { case 'exclusive': case 'non_exclusive': let field_values = array_from_set(field.values).map((id) => this.props.upload_results.values[id]) let user_values_statistics = {} field_values.forEach((value) => { array_from_set(value.entity_ids).forEach((user_id) => { if (filter_user(this.props.upload_results.users[user_id])) { user_values_statistics[user_id] = (user_values_statistics[user_id] || 0) + 1 } }) }) statistics = ( <div className={cx('upload-results__statistics')}> {switcher} <div className={cx('upload-results__content-title')}> Statistiques </div> <Statistics> {field_values.reduce((statistics, value) => { statistics[value.name] = array_from_set(value.entity_ids).map((id) => this.props.upload_results.users[id]).filter(filter_user).length return statistics }, {})} </Statistics> {field.category == 'non_exclusive' ? ( <div> <div className={cx('upload-results__content-title')}> Nombre de valeurs </div> <Statistics> {array_from_hash(user_values_statistics).reduce((statistics, value_count) => { statistics[value_count] = (statistics[value_count] || 0) + 1 return statistics }, {})} </Statistics> </div> ) : null} </div> ) break case 'comment': let all_users = array_from_hash(this.props.upload_results.users).filter(filter_user) let {comments} = this.props.upload_results.fields[this.state.active_field] let filled_users_count = all_users.filter(({id}) => comments[id]).length statistics = ( <div className={cx('upload-results__statistics')}> {switcher} <div className={cx('upload-results__content-title')}> Statistiques </div> <Statistics> {{ "Renseigné": filled_users_count, "Vide": all_users.length - filled_users_count, }} </Statistics> </div> ) break default: } } return ( <div className={cx('upload-results__content-container')}> <div className={cx('upload-results__content-title')}> {field.name} </div> <div className={cx('upload-results__main-content')}> <div className={cx('upload-results__messages')}> {common_message_wrappers} {exists_new_user && new_users_messages.length > 0 ? ( <div className={cx('upload-results__content-title')}> Candidats nouvellement importés </div> ) : null} {exists_new_user ? ( new_users_message_wrappers ) : null} {exists_old_user && old_users_messages.length > 0? ( <div className={cx('upload-results__content-title')}> Candidats déjà présents dans la base </div> ) : null} {exists_old_user ? ( old_users_message_wrappers ) : null} </div> {statistics} </div> <div className={cx('upload-results__upload-button')}> {for_all([...new_users_messages, ...old_users_messages, ...common_messages], (message) => message.informational) ? ( <Button done> {"Vous n'avez rien à faire"} </Button> ) : (imported ? ( <Button done> {new_field ? "Le champ a été créé et les données importées" : "Les données de ce champ ont été importées"} </Button> ) : (this.state.importing[this.state.active_field_type][this.state.active_field] ? ( <Button loading> Importation en cours ... </Button> ) : ( <Button disabled={!for_all([...new_users_messages, ...old_users_messages, ...common_messages], (message) => message.informational || message.validated || message.selected)} onClick={() => { let active_field = this.state.active_field, active_field_type = this.state.active_field_type this.setState(deep_merge(this.state, {importing: {[active_field_type]: {[active_field]: true}}})) this.forceUpdate() let set_imported_state = () => { if (this.mounted) { this.setState(deep_merge(this.state, {imported: {[active_field_type]: {[active_field]: true}}})) } } let messages = this.state.messages[active_field_type][active_field] switch (active_field_type) { case "instant_job_field": this.import_users(active_field) .then(set_imported_state) break case "agency_field": this.import_field(active_field, new_field ? 'overwrite' : (exists_old_user && messages.old.conflict ? messages.old.conflict.selected : 'ignore')) .then(set_imported_state) break default: } }} > { (new_field) ? "Créer le champ" : ((!exists_old_user || (exists_old_user && exists_new_user && for_all(old_users_messages, (message) => message.selected != "overwrite"))) ? "Importer les données pour les nouveaux candidats" : "Importer les données de ce champ") } </Button> )))} </div> </div> ) default: return <div></div> } } render_navigation(field_array) { let current_index = field_array.findIndex((field) => field.active) let decrement = () => { field_array[current_index - 1].onClick() } let increment = () => { field_array[current_index + 1].onClick() } return ( <div className={cx('upload-results__actions')}> <div className={cx('upload-results__actions__button-container', 'upload-results__actions__button-container__reverse')}> {current_index > 0 ? ( <Button secondary left_icon={<MdKeyboardArrowLeft />} onClick={decrement} disabled={current_index == 0}> Précédent </Button> ) : null} </div> <div className={cx('upload-results__actions__button-container')}> {current_index + 1 < field_array.length ? ( <Button secondary right_icon={<MdKeyboardArrowRight />} disabled={field_array[current_index + 1] && field_array[current_index + 1].disabled} onClick={increment}> Suivant </Button> ) : ( <Button secondary right_icon={<MdCheck />} disabled={field_array[current_index + 1] && field_array[current_index + 1].disabled} onClick={this.props.close}> Terminer </Button> )} </div> </div> ) } render() { let status = { invitations: for_all(array_from_hash(this.state.messages.invitations), (message) => message.informational || message.validated || message.selected) ? 'ready' : 'warning', instant_job_field: create_hash(array_from_hash(this.props.instant_job_fields), (field_name) => { let all_messages = this.state.messages.instant_job_field[field_name] || {} let messages = [ ...array_from_hash(all_messages.common), ...array_from_hash(all_messages.new), ...array_from_hash(all_messages.old), ] if (for_all(messages, (message) => message.informational)) { return 'imported' } else if (for_all(messages, (message) => message.informational || message.validated || message.selected)) { return 'ready' } else { return 'warning' } }), agency_field: create_hash(Object.keys(this.props.upload_results.fields), (field_id) => { let all_messages = this.state.messages.agency_field[field_id] || {} let messages = [ ...array_from_hash(all_messages.common), ...array_from_hash(all_messages.new), ...array_from_hash(all_messages.old), ] if (for_all(messages, (message) => message.informational)) { return 'imported' } else if (for_all(messages, (message) => message.informational || message.validated || message.selected)) { return 'ready' } else { return 'warning' } }) } let exists_user = this.props.upload_results.new_users_count > 0 || this.props.upload_results.old_users_count > 0 let field_array = [ { label: "Aperçu", onClick: () => this.setState({active_field_type: 'summary'}), active: this.state.active_field_type == 'summary', disabled: false, icon: <MenuIcon />, display: true, }, { label: "Invitations", onClick: () => this.setState({active_field_type: 'invitations'}), active: this.state.active_field_type == 'invitations', disabled: false, icon: <MenuIcon status={this.state.imported['invitations'] ? 'imported' : status['invitations']} />, display: this.props.upload_results.new_users_count > 0, }, ...array_from_set(this.props.upload_results.instant_job_fields_present) .map((field_label) => { let field_name = this.props.instant_job_fields[field_label] return { label: field_label, onClick: () => this.setState({active_field_type: 'instant_job_field', active_field: field_name}), active: this.state.active_field_type == 'instant_job_field' && this.state.active_field == field_name, disabled: this.props.upload_results.new_users_count > 0 && !this.state.imported['invitations'], icon: <MenuIcon status={this.state.imported['instant_job_field'][field_name] ? 'imported' : status['instant_job_field'][field_name]} />, display: exists_user, } }), ...array_from_hash(this.props.upload_results.fields).map((field) => ( { label: field.name, onClick: () => this.setState({active_field_type: 'agency_field', active_field: field.id}), active: this.state.active_field_type == 'agency_field' && this.state.active_field == field.id, disabled: this.props.upload_results.new_users_count > 0 && !this.state.imported['invitations'], icon: <MenuIcon status={this.state.imported['agency_field'][field.id] ? 'imported' : status['agency_field'][field.id]} />, display: exists_user, } )), ].filter((tab) => tab.display) return ( <div className={cx('upload-results')}> <div className={cx('upload-results__import-button')}> <Button icon={MdFileUpload} secondary onClick={this.props.upload_new_file}> Charger un autre fichier </Button> </div> <div className={cx('upload-results__container')}> <div className={cx('upload-results__menu-container')}> <Menu> {field_array} </Menu> </div> {this.render_page()} </div> {this.render_navigation(field_array)} </div> ) } } const Message = (props) => { let Icon = { 'error': MdError, 'warning': MdWarning, 'ok': MdCheck, }[props.status || 'ok'] return ( <div className={cx('upload-results__message', `upload-results__message_${props.status || 'ok'}`)}> <div className={cx('upload-results__message-title')}> <div className={cx('upload-results__message-title-icon')}> <Icon /> </div> <div className={cx('upload-results__message-title-label')}> {props.title} </div> </div> {props.children ? ( <div className={cx('upload-results__message-information')}> {props.children} </div> ) : null} </div> ) } const MessageFieldsCount = (props) => { let new_field_names_count = props.new_field_names.length if (new_field_names_count > 0) { return ( <Message status={props.new_field_names.length > 0 ? 'warning' : 'ok'} title={human_plural(props.total_fields_count, 'Votre fichier contient %% champ (colonne)', 'Votre fichier contient %% champs (colonnes)')} > <p>{human_plural(props.total_fields_count - props.new_field_names.length, '%% est déjà présent', '%% sont déjà présents')} sur InstantJob</p> <p>{human_plural(props.new_field_names.length, '%% correspond à un nouveau champ', '%% correspondent à des nouveaux champs')} : {human_join(props.new_field_names)}</p> </Message> ) } else { return ( <Message status={props.new_field_names.length > 0 ? 'warning' : 'ok'} title={human_plural(props.total_fields_count, '%% champ a été importé et était déjà présent sur InstantJob', '%% champs ont été importés et étaient déjà présents sur InstantJob')} /> ) } } const MessageInviteUsers = (props) => { let title if (props.new_users == props.all_users) { title = `Votre fichier contient des données au sujet de ${human_plural(props.all_users, '%% nouveau candidat', '%% nouveaux candidats')}.` } else { title = `Votre fichier contient des données au sujet de ${human_plural(props.all_users, '%% candidat', '%% candidats')}.` } return ( <Message status={props.validated ? 'ok' : 'warning'} title={title} > {props.new_users == props.all_users ? null : ( <p>{human_plural(props.all_users - props.new_users, '%% était déjà présent', '%% étaient déjà présents')} sur InstantJob</p> )} {props.new_users == props.all_users ? null : ( <p>{human_plural(props.new_users, '%% est un nouveau candidat', '%% sont des nouveaux candidats')}</p> )} <Action imported={props.imported} status={props.validated ? 'checked' : 'unchecked'} onClick={props.onClick}> {`Créer ${human_plural(props.new_users, '%% nouveau candidat sur InstantJob et lui', '%% nouveaux candidats sur InstantJob et leur')} envoyer une invitation à télécharger l'application.`} </Action> </Message> ) } const MessageAbsentFields = (props) => ( <Message status={props.validated ? 'ok' : 'warning'} title={`${human_plural(props.absent_fields.length, `Le champ "${props.absent_fields[0]}" n'a pas été trouvé`, `Les champs ${human_join(props.absent_fields.map((field) => `"${field}"`))} n'ont pas été trouvés`)} dans votre fichier.`} > <Action imported={props.imported} status={props.validated ? 'checked' : 'unchecked'} onClick={props.onClick}> {`Laisser ${human_plural(props.absent_fields.length, 'ce champ vide', 'ces champs vides')} pour les nouveaux candidats importés.`} </Action> </Message> ) const MessageInstantJobFieldNew = (props) => ( <Message title={`Les données ont déjà été importées car il s'agit d'un champ imposé par InstantJob.`} /> ) const MessageAgencyFieldReady = (props) => ( <Message title={`Données prêtes à être importées.`} /> ) const MessageFieldIdentical = (props) => ( <Message title={`Les données du fichier sont identiques aux données dans InstantJob.`} /> ) const MessageFieldConflict = (props) => { let new_field_values_count = (props.new_field_values || []).length return ( <Message status={props.selected ? 'ok' : 'warning'} title={`Pour ${human_plural(props.conflicting_users_count, '%% candidat', '%% candidats')}, les données de votre fichier ne correspondent pas aux données présentes dans votre base InstantJob.`} > {new_field_values_count ? ( <p>Nous avons également trouvé {human_plural(new_field_values_count, "%% nouvelle valeur", "%% nouvelles valeurs")} chez les candidats déjà présents : {human_join(props.new_field_values, 10)}</p> ) : null} {props.exists_new_user ? ( <Action imported={props.imported} status={props.selected == 'ignore' ? 'checked' : (props.selected ? 'discarded' : 'unchecked')} onClick={() => props.on_select('ignore')}> Ne pas prendre en compte les données importées et laisser la base InstantJob inchangée </Action> ) : null} <Action imported={props.imported} status={props.selected == 'overwrite' ? 'checked' : (props.selected && props.exists_new_user ? 'discarded' : 'unchecked')} onClick={props.exists_new_user || !props.selected ? () => props.on_select('overwrite') : () => props.on_select(false)} > Ecraser les données sur InstantJob et les remplacer par les données de votre fichier {new_field_values_count > 0 ? `en créant ${human_plural(new_field_values_count, "%% nouvelle valeur", "%% nouvelles valeurs")}` : null} </Action> </Message> ) } const MessageInstantJobAppUserConflict = (props) => ( <Message status={'warning'} title={`Les données de ${human_plural(props.conflicting_users_count, "%% candidat ne pourront pas être modifiées car il indique", "%% candidats ne pourront pas être modifiées car ils indiquent")} ces informations via l'application.`} /> ) const MessageNewField = (props) => ( <Message status={props.selected ? 'ok' : 'warning'} title={"Ce champ n'existe pas dans la base InstantJob"}> <p>Ce champ correspond-il a un champ de tri (à choix multiple) ou à un champ de commentaire ?</p> <FieldCategoriesSelector selected={props.selected} on_select={props.imported ? () => {} : props.on_select} /> </Message> ) const MessageDuplicateUsers = (props) => ( <Message status={'warning'} title={`Votre fichier contient ${human_plural(props.duplicate_users_count, "%% addresse mail", "%% adresses mail")} en double`} > <p>Nous avons conservé uniquement la dernière ligne correspondant à chaque adresse mail dupliquée</p> </Message> ) const MessageInvalidEmails = (props) => ( <Message status={'warning'} title={`Votre fichier contient ${human_plural(props.invalid_emails.length, "%% addresse mail invalide", "%% adresses mail invalides")} : ${human_join(props.invalid_emails, 10)}`} > <p>Il faut une adresse mail valide pour envoyer une invitation, {human_plural(props.invalid_emails.length, "ce candidat ne pourra donc pas être importé", "ces candidats ne pourront donc pas être importés")}.</p> </Message> ) const MessageEmptyFile = (props) => ( <Message status={'error'} title={`Votre fichier ne contient aucune adresse mail valide`} /> ) const MessageNewValuesNewUsers = (props) => ( <Message status={props.validated ? 'ok' : 'warning'} title={`Nous avons trouvé ${human_plural(props.new_field_values.length, "%% nouvelle valeur", "%% nouvelles valeurs")} chez ${human_plural(props.new_users_count, "le nouveau candidat", "les %% nouveaux candidats")} : ${human_join(props.new_field_values, 10)}`} > <Action imported={props.imported} status={props.validated ? 'checked' : 'unchecked'} onClick={props.onClick}> Créer {human_plural(props.new_field_values.length, "%% nouvelle valeur", "%% nouvelles valeurs")} </Action> </Message> ) const Action = (props) => { let Icon = props.status == 'checked' ? MdCheckBox : MdCheckBoxOutlineBlank return ( <div className={cx('upload-results__message-action', {[`upload-results__message-action_${props.status}`]: props.status, 'upload-results__message-action_editable': !props.imported})} onClick={props.imported ? () => {} : props.onClick}> <div className={cx('upload-results__message-action-checkbox')}> <Icon /> </div> <div className={cx('upload-results__message-action-label')}> {props.children} </div> </div> ) } const MenuIcon = (props) => { switch (props.status) { case 'imported': return ( <div className={cx('upload-results__menu-icon', 'upload-results__menu-icon_imported')}> <MdCheck /> </div> ) case 'ready': return ( <div className={cx('upload-results__menu-icon', 'upload-results__menu-icon_ready')}></div> ) case 'warning': return ( <div className={cx('upload-results__menu-icon', 'upload-results__menu-icon_warning')}></div> ) case 'error': return ( <div className={cx('upload-results__menu-icon', 'upload-results__menu-icon_error')}></div> ) default: return <div></div> } } const Statistics = (props) => ( <div style={{display: 'flex', flexDirection: 'column', alignItems: 'stretch'}}> {Object.keys(props.children).map((label, index) => ( <div key={index} style={{display: 'flex', justifyContent: 'space-between'}}> <div> {label} </div> <div> {props.children[label]} </div> </div> ))} </div> ) const UploadResults = connect( (state) => ({ fields: state.fields.fields, values: state.fields.values, users: get_users(state), }), )(UploadResultsStatic) UploadResults.defaultProps = { upload_results: { fields: [], values: {}, users: [], }, fields: {}, values: {}, users: {}, update_upload_results: () => {}, upload_new_file: () => {}, close: () => {}, } export default UploadResults