instantjob-recruiter-client
Version:
a set of tools for creating an instantjob recruiter react client
389 lines (354 loc) • 10.7 kB
JSX
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 <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;
`