import {Component, Fragment} from 'react'; import styled from '@emotion/styled'; import flatten from 'lodash/flatten'; import Alert from 'sentry/components/alert'; import Button from 'sentry/components/button'; import Confirm from 'sentry/components/confirm'; import FormField from 'sentry/components/forms/formField'; import {TableType} from 'sentry/components/forms/types'; import Input from 'sentry/components/input'; import {IconAdd, IconDelete} from 'sentry/icons'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import {defined, objectIsEmpty} from 'sentry/utils'; import {singleLineRenderer} from 'sentry/utils/marked'; // XXX(epurkhiser): This is wrong, it should not be inheriting these props import {InputFieldProps} from './inputField'; interface DefaultProps { /** * Text used for the 'add' button. An empty string can be used * to just render the "+" icon. */ addButtonText: string; /** * Automatically save even if fields are empty */ allowEmpty: boolean; } export interface TableFieldProps extends Omit {} interface RenderProps extends TableFieldProps, DefaultProps, Omit {} const DEFAULT_PROPS: DefaultProps = { addButtonText: t('Add Item'), allowEmpty: false, }; export default class TableField extends Component { static defaultProps = DEFAULT_PROPS; hasValue = value => defined(value) && !objectIsEmpty(value); renderField = (props: RenderProps) => { const { onChange, onBlur, addButtonText, columnLabels, columnKeys, disabled: rawDisabled, allowEmpty, confirmDeleteMessage, } = props; const mappedKeys = columnKeys || []; const emptyValue = mappedKeys.reduce((a, v) => ({...a, [v]: null}), {id: ''}); const valueIsEmpty = this.hasValue(props.value); const value = valueIsEmpty ? (props.value as any[]) : []; const saveChanges = (nextValue: object[]) => { onChange?.(nextValue, []); // nextValue is an array of ObservableObjectAdministration objects const validValues = !flatten(Object.values(nextValue).map(Object.entries)).some( ([key, val]) => key !== 'id' && !val // don't allow empty values except if it's the ID field ); if (allowEmpty || validValues) { // TOOD: add debouncing or use a form save button onBlur?.(nextValue, []); } }; const addRow = () => { saveChanges([...value, emptyValue]); }; const removeRow = rowIndex => { const newValue = [...value]; newValue.splice(rowIndex, 1); saveChanges(newValue); }; const setValue = ( rowIndex: number, fieldKey: string, fieldValue: React.FormEvent ) => { const newValue = [...value]; newValue[rowIndex][fieldKey] = fieldValue.currentTarget ? fieldValue.currentTarget.value : null; saveChanges(newValue); }; // should not be a function for this component const disabled = typeof rawDisabled === 'function' ? false : rawDisabled; const button = ( ); // The field will be set to inline when there is no value set for the // field, just show the button. if (!valueIsEmpty) { return
{button}
; } const renderConfirmMessage = () => { return ( ); }; return ( {mappedKeys.map((fieldKey, i) => (
{columnLabels?.[fieldKey]} {i === mappedKeys.length - 1 && button}
))}
{value.map((row, rowIndex) => ( {mappedKeys.map((fieldKey: string, i: number) => ( setValue(rowIndex, fieldKey, v)} value={!defined(row[fieldKey]) ? '' : row[fieldKey]} /> {i === mappedKeys.length - 1 && ( removeRow(rowIndex)} message={renderConfirmMessage()} >