UNPKG

instantjob-recruiter-client

Version:

a set of tools for creating an instantjob recruiter react client

389 lines (354 loc) 10.7 kB
import React, {Component} from 'react' import styled from 'styled-components' import classNames from 'classnames/bind' import { MdSettings, MdCheckBox, MdCheckBoxOutlineBlank, MdKeyboardArrowUp, MdKeyboardArrowDown, MdAddCircle, MdLock, MdWarning, } from 'react-icons/lib/md' import Button from 'components/utils/button' import TimeAgo from 'components/utils/time_ago' import MoreButton from './more_button' import FileDropzone from 'components/file_dropzone' import CodeEditor from 'components/code_editor' import store from 'common/store' import event_system from 'common/event_system' import auto_bind from 'common/auto_bind' import {color, link} from 'common/styles' import {array_contains, empty_array, download_file} from 'common/utilities' import {action_update_field, action_update_field_template} from 'common/fields' import {alert_warning} from 'actions/display' const cx = classNames.bind(require('../styles/fields_table.scss')) const value_width = 300 const icon_width = 40 class FieldsTable extends Component { constructor(props) { super(props) this.state = { width: 300, } this.update_width = this.update_width.bind(this) this.render_option = this.render_option.bind(this) this.render_field = this.render_field.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.fields_table).width() if (width != this.state.width) { this.setState({width}) } } render_option({name}, index) { return ( <div key={index} className={cx('fields-table__header-options-option')}> {name} </div> ) } render_field(field) { return ( <Field {...field} field={field} width={this.state.width} key={field.id} options={this.props.options} /> ) } render() { return ( <div ref={(fields_table) => this.fields_table = fields_table} className={cx('fields-table')}> <div className={cx('fields-table__header')}> <div className={cx('fields-table__header-name')}> Nom du champ </div> <div className={cx('fields-table__header-options')}> {this.props.options.map(this.render_option)} </div> </div> {this.props.fields.length ? ( <div className={cx('fields-table__fields')}> {this.props.fields.map(this.render_field)} </div> ) : ( <div className={cx('fields-table__fields-placeholder')}> Il n'y a aucun champ. </div> )} </div> ) } } class Field extends Component { constructor(props) { super(props) this.state = { open: false, editing: false, } auto_bind(this) } toggle_editing() { this.setState(({editing}) => ({editing: !editing})) } toggle_open() { if (!this.props.locked && this.props.editable) { this.setState(({open}) => ({open: !open})) } } toggle_option(toggle) { if (this.props.locked) { return null } else { return (event) => { event.stopPropagation() if (this.props.editable) { action_update_field(toggle(this.props.field)) } else { this.props.update(toggle(this.props.field)) } } } } on_change_name(name) { this.props.update({name}) this.toggle_editing() } update_code(code) { this.props.update({code}) } stop_recycling() { this.props.update({recycled: false}) } create_value() { this.props.create_value() } update_template([template]) { const {id} = this.props action_update_field_template(id, template) } download_template() { const {template: {url, name}} = this.props download_file(url, name) } render_option({get, toggle}, index) { return ( <div key={index} onClick={this.toggle_option(toggle)} className={cx('fields-table__field-header-options-option')}> {get(this.props.field) ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />} </div> ) } render_value(value) { let available_width = this.props.width - 2 * icon_width - 2 let columns_number = Math.floor(available_width / value_width) || 1 let width = available_width / columns_number let {id} = value return ( <Value {...value} width={width} key={id} field_name={this.props.name} /> ) } render_details() { const {category, template, values, code} = this.props switch (category) { case "exclusive": case "non_exclusive": return [ ...values.map(this.render_value), <Button key={'new-value'} discreet icon={MdAddCircle} className={cx('fields-table__field-details-new-value')} onClick={this.create_value} > Nouvelle Valeur </Button> ] case "comment": return ( <div className={cx('fields-table__field-details-simple')}> Champ de commentaire </div> ) case "file": return ( <div className={cx('fields-table__field-details-simple')}> Document </div> ) case "template": return ( <div className={cx('fields-table__field-details-simple', 'fields-table__field-details-simple_template')}> {template.url ? ( <DocumentInformation> Mis à jour&nbsp;<TimeAgo>{template.updated_at}</TimeAgo> <Download onClick={this.download_template}>Voir</Download> </DocumentInformation> ) : null} <FileDropzone on_files_drop={this.update_template}> Modifier le modèle de document </FileDropzone> </div> ) case "computed": return ( <div className={cx('fields-table__field-details-simple')}> <CodeEditor value={code} on_change={this.update_code} /> </div> ) default: return <span style={{color: 'pink', padding: '8px 0'}}>{category}</span> } } render() { let {open, editing} = this.state let { editable, locked, for_entity, recycled, options, name, category, values, template, remove, show_associated_values, } = this.props let error_message = "" switch (category) { case 'exclusive': case 'non_exclusive': if (empty_array(values)) { error_message = "Ce champ n'a pas de valeur." } break case 'template': if (!template.url) { error_message = "Le fichier modèle n'a pas été téléchargé." } break } return ( <div className={cx('fields-table__field', { 'fields-table__field_open': open, 'fields-table__field_locked': locked, 'fields-table__field_unlocked': !locked, 'fields-table__field_editable': editable, })} > <div onClick={this.toggle_open} className={cx('fields-table__field-header')}> {editing ? ( <Input value={name} on_change={this.on_change_name} auto_focus /> ) : ( <div className={cx('fields-table__field-header-name')}> <div className={cx('fields-table__field-header-name-label')}> {name} </div> {editable && !locked ? ( <MoreButton start_edit={this.toggle_editing} on_delete={recycled ? null : remove} stop_recycling={recycled ? this.stop_recycling : null} show_associated_values={show_associated_values} /> ) : null} {error_message ? ( <div className={cx('fields-table__field-header-name-warning')}> <MdWarning /> <span className={cx('fields-table__field-header-name-warning-label')}> {error_message} </span> </div> ) : null} </div> )} <div className={cx('fields-table__field-header-options')}> {options.map(this.render_option)} </div> <div className={cx('fields-table__field-header-toggle-open')}> {locked ? ( <MdLock /> ) : (editable ? ( open ? <MdKeyboardArrowUp /> : <MdKeyboardArrowDown /> ) : null)} </div> </div> {open ? ( <div className={cx('fields-table__field-details')}> {this.render_details()} </div> ) : null} </div> ) } } Field.defaultProps = {editable: true, locked: false} class Value extends Component { constructor(props) { super(props) auto_bind(this) } render() { const {options, name, width, show_associated_values} = this.props return ( <div className={cx('fields-table__value')} style={{width}}> <div onClick={show_associated_values} className={cx('fields-table__value-label', {'fields-table__value-label_empty': !name})}> {name || "Valeur sans nom"} </div> </div> ) } } class Input extends Component { constructor(props) { super(props) this.state = { value: props.value, } auto_bind(this) } componentWillReceiveProps({value}) { if (value != this.props.value) { this.setState({value}) } } componentDidMount() { if (this.props.auto_focus) { this.input.focus() } } onChange({target: {value}}) { if (array_contains([',', ';'], value.slice(-1))) { store.dispatch(alert_warning(`Le caractère '${value.slice(-1)}' n'est pas autorisé dans le nom d'un champ.`)) value = value.slice(0, -1) } this.setState({value}) } onBlur() { this.props.on_change(this.state.value.trim()) } render() { return ( <input ref={(input) => this.input = input} type="text" placeholder="Nom" className={cx('fields-table__input')} value={this.state.value || ""} onChange={this.onChange} onBlur={this.onBlur} /> ) } } FieldsTable.defaultProps = { fields: [], options: [], } export default FieldsTable const DocumentInformation = styled.div` padding: 5px 0; display: flex; ` const Download = styled.div` ${link} color: ${color('black', 'bright')}; text-decoration: underline; padding: 0 5px; `