/* eslint-disable react/no-multi-comp */ import React from 'react'; import ReactPaginate from 'react-paginate'; import ObjectEditor from './ObjectEditor'; import {once, debounce} from 'lodash'; import {gettext} from 'core/utils'; import {ISortOption, IVocabularyItem} from 'superdesk-api'; import {assertNever} from 'core/helpers/typescript-helpers'; import {Dropdown} from 'core/ui/components/Dropdown/Dropdown'; import {Menu} from 'core/ui/components/Dropdown/Menu'; import {dataApi} from 'core/helpers/CrudManager'; import {ILanguage} from 'superdesk-interfaces/Language'; import {ManageVocabularyItemTranslations} from '../ManageVocabularyItemTranslations'; interface ISchemaField { key: string; label?: string; type?: 'object' | string; required?: boolean; } interface IVocabularyItemWithId extends IVocabularyItem { id: string; } const pageSize = 50; function getPageCount(items: Array) { return Math.ceil(items.length / pageSize); } function sortItems(items: Array, sort: ISortOption): Array { return [...items].sort((a, b) => { if (sort.direction === 'ascending') { if (a[sort.field] < b[sort.field]) { return -1; } else if (a[sort.field] > b[sort.field]) { return 1; } else { return 0; } } else if (sort.direction === 'descending') { if (a[sort.field] < b[sort.field]) { return 1; } else if (a[sort.field] > b[sort.field]) { return -1; } else { return 0; } } else { return assertNever(sort.direction); } }); } interface IPropsInputField { field: ISchemaField; item: IVocabularyItemWithId; required: boolean; update(item: any, key: string, value: any): void; } function isItemFieldValid(item: IVocabularyItemWithId, field: ISchemaField): boolean { if (!field.required) { return true; } else if (typeof item[field.key] === 'string') { return item[field.key].length > 0; } else { return item[field.key] != null; } } function containsInvalidItems(items: Array, schemaFields: Array): boolean { return items.some((item) => { return schemaFields.some((field) => { const valid = isItemFieldValid(item, field); return !valid; }); }); } class InputField extends React.PureComponent { render() { const {field, item, required} = this.props; const value = item[field.key] || ''; const disabled = !item.is_active; let className = 'sd-line-input sd-line-input--no-margin sd-line-input--no-label sd-line-input--boxed'; if (required) { className += ' sd-line-input--required'; } if (!isItemFieldValid(item, field)) { className += ' sd-line-input--invalid'; } switch (field.type) { case 'bool': return ( this.props.update(item, field.key, !value)} /> ); case 'color': return ( this.props.update(item, field.key, event.target.value)} /> ); case 'short': return ( { this.props.update(item, field.key, event.target.value); }} /> ); case 'object': { return ( this.props.update(item, field.key, _value)} /> ); } case 'integer': return (
{ this.props.update(item, field.key, parseInt(event.target.value, 10)); }} />
); default: return (
{ this.props.update(item, field.key, event.target.value); }} data-test-id={'field--' + field.key} />
); } } } interface IProps { items: Array; schemaFields: Array; newItemTemplate: any; setDirty(): void; setItemsValid(valid: boolean): void; } interface IState { items: Array; page: number; searchTerm: string; searchExtended: boolean; sortDropdownOpen: boolean; sort: ISortOption | null; errorMessage: string | null; languages: Array | null; } export class VocabularyItemsViewEdit extends React.Component { static propTypes: any; static defaultProps: any; sortFields: Array; lastId: number; generateId: () => string; setDirtyOnce: () => void; setValidItemsDebounced: () => void; constructor(props: IProps) { super(props); this.sortFields = this.props.schemaFields.map((field) => field.key); this.lastId = 0; this.generateId = () => (this.lastId++).toString(); const initialSortOption: ISortOption = { field: this.sortFields.includes('name') ? 'name' : this.sortFields[0], direction: 'ascending', }; this.state = { items: sortItems( props.items.map((item) => ({...item, id: this.generateId()})), initialSortOption, ), page: 1, searchTerm: '', searchExtended: false, sortDropdownOpen: false, sort: initialSortOption, errorMessage: null, languages: null, }; this.updateItem = this.updateItem.bind(this); this.removeItem = this.removeItem.bind(this); this.addItem = this.addItem.bind(this); this.getItemsForSaving = this.getItemsForSaving.bind(this); this.setErrorMessage = this.setErrorMessage.bind(this); this.setDirtyOnce = once(this.props.setDirty); this.setValidItemsDebounced = debounce( () => { this.props.setItemsValid(!containsInvalidItems(this.state.items, this.props.schemaFields)); }, 200, ); } private updateItem(item: any, key: string, value: any) { this.setState({ items: this.state.items.map((_item) => { if (item === _item) { return {..._item, [key]: value}; } else { return _item; } }), }); } private removeItem(item: any) { this.setState({ items: this.state.items.filter((_item) => _item !== item), }); } private addItem() { this.setState({ items: [{...this.props.newItemTemplate, id: this.generateId()}].concat(this.state.items), }); } // tslint:disable-next-line: member-access public getItemsForSaving(): Array { // will be used from a ref return this.state.items.map((item) => { const nextItem = {...item}; delete nextItem['id']; return nextItem; }); } // tslint:disable-next-line: member-access public setErrorMessage(message: string | null) { this.setState({errorMessage: message}); } componentDidMount() { this.setValidItemsDebounced(); dataApi.query( 'languages', 1, {field: 'language', direction: 'ascending'}, {}, ).then((res) => { this.setState({languages: res._items}); }); } componentDidUpdate(prevProps: IProps, prevState: IState) { const sortOptionChanged = prevState.sort !== this.state.sort; if (sortOptionChanged) { // eslint-disable-next-line react/no-did-update-set-state this.setState({ items: sortItems(this.state.items, this.state.sort), }); } if (this.state.items !== prevState.items) { this.setDirtyOnce(); this.setValidItemsDebounced(); } } render() { const {languages} = this.state; if (languages == null) { return null; } const takeFrom = (this.state.page - 1) * pageSize; const takeTo = this.state.page * pageSize; const filteredItems = this.state.searchTerm.length < 1 ? this.state.items : this.state.items.filter((item) => { return item.qcode?.toLocaleLowerCase().includes(this.state.searchTerm) || item.name?.toLocaleLowerCase().includes(this.state.searchTerm); }); return (
{ this.setState({searchTerm: event.target.value, page: 1}); }} />
{this.state.sort == null ? null : ( {this.sortFields.map((field) => { return (
  • ); })}
    )} {this.state.sort == null ? null : ( )}
    { this.setState({page: selected + 1}); }} forcePage={this.state.page - 1} containerClassName="bs-pagination" activeClassName="active" />
    {this.props.schemaFields.map((field) => ( ))} {filteredItems.slice(takeFrom, takeTo).map((item) => { return ( {this.props.schemaFields.map((field) => { return ( ); })} ); })}
    { field.key === 'name' && (
    { this.updateItem(item, _field, value); }} languages={languages} />
    ) }
    this.updateItem(item, 'is_active', !item.is_active) } />
    { this.state.errorMessage == null ? null : (

    {this.state.errorMessage}

    ) }
    ); } }