diff --git a/frontend/components/newProduct/Step3.js b/frontend/components/newProduct/Step3.js index 933b02b..8d9b29b 100644 --- a/frontend/components/newProduct/Step3.js +++ b/frontend/components/newProduct/Step3.js @@ -5,71 +5,202 @@ import { Box, Button, FormControl, + MenuItem, Stack, TextField, Typography } from '@mui/material' import IconButton from '@mui/material/IconButton' import InputAdornment from '@mui/material/InputAdornment' -import MenuItem from '@mui/material/MenuItem' +import debounce from 'lodash/debounce' import PropTypes from 'prop-types' -import React, { useEffect, useRef, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { contentAssociation, getProductContents } from '../../services/product' import Loading from '../Loading' -import axios from 'axios' + +const ucds = [ + { + name: 'ID', + value: 'meta.id;meta.main' + }, + { + name: 'RA', + value: 'pos.eq.ra;meta.main' + }, + { + name: 'Dec', + value: 'pos.eq.dec;meta.main' + }, + { + name: 'z', + value: 'src.redshift' + }, + { + name: 'z_err', + value: 'stat.error;src.redshift' + }, + { + name: 'z_flag', + value: 'stat.rank' + }, + { + name: 'survey', + value: 'meta.curation' + } +] +export function InputReadOnly({ name, value, onClear }) { + + return ( + + + + + + + ) + } : null} + /> + + ) +} +InputReadOnly.propTypes = { + value: PropTypes.string.isRequired, + name: PropTypes.string, + onClear: PropTypes.func +} + +export function InputUcd({ pc, options, onChange, onChangeInputType }) { + const [value, setValue] = useState('') + + const handleChange = (e) => { + setValue(e.target.value) + onChange(pc, e.target.value) + } + const handleChangeType = () => { + onChangeInputType(pc.column_name) + } + return ( + + + + {options.map(ucd => ( + + {ucd.name} + + ))} + + + + + + + ) +} +InputUcd.propTypes = { + pc: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + onChangeInputType: PropTypes.func.isRequired, + options: PropTypes.array.isRequired +} + +export function InputAlias({ pc, onChange, onChangeInputType }) { + + const [value, setValue] = useState('') + + // Using lodash debounce to Delay search by 600ms + // Exemplo: https://www.upbeatcode.com/react/how-to-use-lodash-in-react/ + // eslint-disable-next-line react-hooks/exhaustive-deps + const delayedEdit = useCallback( + debounce((pc, alias) => onChange(pc, alias), 600), + [] + ) + + const handleChange = (e) => { + setValue(e.target.value) + delayedEdit(pc, e.target.value) + } + + const handleClear = () => { + setValue('') + onChange(pc, null) + } + + const handleChangeType = () => { + onChangeInputType(pc.column_name) + } + + return ( + + + + + + + + ) + }} + > + + + + + + + ) +} +InputAlias.propTypes = { + pc: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + onChangeInputType: PropTypes.func.isRequired, +} + export default function NewProductStep3({ productId, onNext, onPrev }) { - const ucds = [ - { - name: 'ID', - value: 'meta.id;meta.main' - }, - { - name: 'RA', - value: 'pos.eq.ra;meta.main' - }, - { - name: 'Dec', - value: 'pos.eq.dec;meta.main' - }, - { - name: 'z', - value: 'src.redshift' - }, - { - name: 'z_err', - value: 'stat.error;src.redshift' - }, - { - name: 'z_flag', - value: 'stat.rank' - }, - { - name: 'survey', - value: 'meta.curation' - } - ] const [productColumns, setProductColumns] = React.useState([]) const [usedUcds, setUsedUcds] = React.useState([]) const [isLoading, setLoading] = useState(false) const [formError, setFormError] = React.useState('') - const [editableFields, setEditableFields] = useState({}) - const editFieldRefs = useRef({}) + const [inputsType, setInputsType] = useState([]) + const loadContents = React.useCallback(async () => { setLoading(true) try { const response = await getProductContents(productId) + setProductColumns(response.results) - const aliases = {} - response.results.forEach(row => { - if (row.alias) { - aliases[row.column_name] = row.alias - } - }) - setEditableFields(aliases) setLoading(false) } catch (error) { setLoading(false) @@ -79,6 +210,24 @@ export default function NewProductStep3({ productId, onNext, onPrev }) { } }, [productId]) + const changeProductContent = (pc, ucd, alias) => { + if (pc.ucd === ucd && pc.alias === alias) { + return + } + // setLoading(true) + contentAssociation(pc.id, ucd, alias) + .then(() => { + // setLoading(false) + loadContents(productId) + }) + .catch(res => { + setLoading(false) + if (res.response.status === 500) { + catchFormError(res.response.data) + } + }) + } + useEffect(() => { loadContents() }, [loadContents]) @@ -99,157 +248,89 @@ export default function NewProductStep3({ productId, onNext, onPrev }) { const handlePrev = () => { onPrev(productId) } - const onChangeUcd = (pc, value) => { - if (pc.ucd === value) { - return - } - // setLoading(true) - contentAssociation(pc.id, value) - .then(() => { - // setLoading(false) - loadContents(productId) - }) - .catch(res => { - setLoading(false) - if (res.response.status === 500) { - catchFormError(res.response.data) - } - }) - } - const handleMouseDownPassword = event => { - event.preventDefault() + const onClear = (pc) => { + console.log("onClear: ", pc) + changeProductContent(pc, null, null) } - const handleAlias = (pc, value) => { - setEditableFields(prevState => ({ - ...prevState, - [pc.column_name]: value - })) + const onSelectUcd = (pc, ucd) => { + console.log("onSelectUcd: ", pc, ucd) + changeProductContent(pc, ucd, getAliasByUcd(ucd)) + } - axios.patch(`/api/product-contents/${pc.id}/`, { alias: value }) - .then(response => { - }) - .catch(error => { - }) + const onChangeAlias = (pc, alias) => { + console.log("onChangeAlias: ", pc, alias) + changeProductContent(pc, null, alias) } - const handleCancelEdit = pc => { - const updatedEditableFields = { ...editableFields } - delete updatedEditableFields[pc.column_name] - setEditableFields(updatedEditableFields) + const getAliasByUcd = (ucd) => { + const result = ucds.find(o => o.value === ucd); + return result ? result.name : null } - const createSelect = pc => { - // Check Available Ucds and Current UCD when pc.ucd is not null + const getAvailableUcds = () => { const avoptions = [] - let currentUcd = null ucds.forEach(ucd => { - if (usedUcds.indexOf(ucd.value) === -1 || ucd.value === pc.ucd) { + if (usedUcds.indexOf(ucd.value) === -1) { avoptions.push(ucd) } - if (ucd.value === pc.ucd) { - currentUcd = ucd - } }) + return avoptions + } - const isOptionSelected = pc.ucd !== null + const isInputTypeAlias = name => { + // Se name estiver no array de input type + // Entao Input é do tipo Alias. + if (inputsType.indexOf(name) === -1) { + return false + } + return true + } - return ( - - {isOptionSelected ? ( - - onChangeUcd(pc, null)}> - - - - ), - }} - /> - ) : ( - <> - {pc.column_name in editableFields ? ( - - handleAlias(pc, e.target.value)} - onBlur={() => { - // console.log(`${pc.column_name}: ${editableFields[pc.column_name]}`) - handleAlias(pc, editableFields[pc.column_name]) - }} - onKeyPress={event => { - if (event.key === 'Enter' || event.key === 'Tab') { - handleAlias(pc, editableFields[pc.column_name]) - // console.log(`${pc.column_name}: ${editableFields[pc.column_name]}`) - editFieldRefs.current[pc.column_name].blur() - } - }} - InputProps={{ - endAdornment: ( - - handleCancelEdit(pc)}> - - - - ) - }} - inputRef={ref => { - editFieldRefs.current[pc.column_name] = ref - }} - autoFocus - /> - - ) : ( - - onChangeUcd(pc, e.target.value)} - > - {avoptions.map(ucd => ( - - {ucd.name} - - ))} - - handleAlias(pc, '')} - onMouseDown={handleMouseDownPassword} - > - - - - )} - - )} - - ) + const handleChangeInputType = column_name => { + if (inputsType.indexOf(column_name) === -1) { + inputsType.push(column_name) + } else { + inputsType.splice(inputsType.indexOf(column_name), 1); + } + loadContents(productId) } const createFields = pc => { + + const avoptions = getAvailableUcds() + + if (pc.ucd !== null || pc.alias !== null) { + return ( + onClear(pc)} /> + ) + } + + // Campo de Texto para Alias + if (isInputTypeAlias(pc.column_name) === true) { + return ( + + ) + } + + // Select para UCD return ( - - - - - {createSelect(pc)} - + ) } + const catchFormError = data => { let msg = 'There was a failure, please try again later, if the problem persists, please contact support.' @@ -289,7 +370,18 @@ export default function NewProductStep3({ productId, onNext, onPrev }) { autoComplete="off" > {productColumns.map(pc => { - return createFields(pc) + + return ( + + + {createFields(pc)} + + ) })} {formError !== '' && handleFormError()} diff --git a/frontend/components/newProduct/Step3_copy.js b/frontend/components/newProduct/Step3_copy.js new file mode 100644 index 0000000..a6227e6 --- /dev/null +++ b/frontend/components/newProduct/Step3_copy.js @@ -0,0 +1,362 @@ +import CloseIcon from '@mui/icons-material/Close' +import EditIcon from '@mui/icons-material/Edit' +import { + Alert, + Box, + Button, + FormControl, + Stack, + TextField, + Typography +} from '@mui/material' +import IconButton from '@mui/material/IconButton' +import InputAdornment from '@mui/material/InputAdornment' +import MenuItem from '@mui/material/MenuItem' +import debounce from 'lodash/debounce' +import PropTypes from 'prop-types' +import React, { useCallback, useEffect, useState } from 'react' +import { contentAssociation, getProductContents } from '../../services/product' +import Loading from '../Loading' + + +export default function InputUcd({ isLoading }) { + + return ( + theme.zIndex.drawer + 1 }} + open={isLoading} + > + + + ) +} +InputUcd.propTypes = { + isLoading: PropTypes.bool.isRequired +} + +export default function InputAlias({ }) { + + return ( + theme.zIndex.drawer + 1 }} + open={isLoading} + > + + + ) +} +InputAlias.propTypes = { + isLoading: PropTypes.bool.isRequired +} + + +export default function NewProductStep3({ productId, onNext, onPrev }) { + const ucds = [ + { + name: 'ID', + value: 'meta.id;meta.main' + }, + { + name: 'RA', + value: 'pos.eq.ra;meta.main' + }, + { + name: 'Dec', + value: 'pos.eq.dec;meta.main' + }, + { + name: 'z', + value: 'src.redshift' + }, + { + name: 'z_err', + value: 'stat.error;src.redshift' + }, + { + name: 'z_flag', + value: 'stat.rank' + }, + { + name: 'survey', + value: 'meta.curation' + } + ] + + const [productColumns, setProductColumns] = React.useState([]) + const [usedUcds, setUsedUcds] = React.useState([]) + const [isLoading, setLoading] = useState(false) + const [formError, setFormError] = React.useState('') + const [editableFields, setEditableFields] = useState({}) + // const editFieldRefs = useRef({}) + + const loadContents = React.useCallback(async () => { + setLoading(true) + try { + const response = await getProductContents(productId) + setProductColumns(response.results) + + // const aliases = {} + // response.results.forEach(row => { + // if (row.alias) { + // aliases[row.column_name] = row.alias + // } + // }) + // setEditableFields(aliases) + + setLoading(false) + } catch (error) { + setLoading(false) + if (error.response && error.response.status === 500) { + catchFormError(error.response.data) + } + } + }, [productId]) + + useEffect(() => { + loadContents() + }, [loadContents]) + + useEffect(() => { + const useducds = [] + productColumns.forEach(row => { + if (row.ucd !== null) { + useducds.push(row.ucd) + } + }) + setUsedUcds(useducds) + }, [productColumns]) + + const handleSubmit = () => { + onNext(productId) + } + const handlePrev = () => { + onPrev(productId) + } + + const onSelectUcd = (pc, value) => { + const ucd = ucds.find(o => o.value === value); + onChangeProductContent(pc, ucd.value, ucd.name) + } + + // Using lodash debounce to Delay search by 600ms + // Exemplo: https://www.upbeatcode.com/react/how-to-use-lodash-in-react/ + // eslint-disable-next-line react-hooks/exhaustive-deps + const delayedEdit = useCallback( + debounce((pc, alias) => onChangeProductContent(pc, null, alias), 600), + [] + ) + + const onChangeAlias = (pc, alias) => { + // console.log('onChangeAlias: ', pc.column_name, value) + delayedEdit(pc, alias) + } + + const onChangeProductContent = (pc, ucd, alias) => { + if (pc.ucd === ucd && pc.alias === alias) { + return + } + // setLoading(true) + contentAssociation(pc.id, ucd, alias) + .then(() => { + // setLoading(false) + loadContents(productId) + }) + .catch(res => { + setLoading(false) + if (res.response.status === 500) { + catchFormError(res.response.data) + } + }) + } + + const handleEdit = (pc) => { + setEditableFields(prevState => ({ + ...prevState, + [pc.column_name]: pc.value + })) + } + + const handleCancelEdit = pc => { + console.log("handleCancelEdit: ", pc.column_name) + // const updatedEditableFields = { ...editableFields } + // delete updatedEditableFields[pc.column_name] + // setEditableFields(updatedEditableFields) + onChangeProductContent(pc, null, null) + } + + const createSelect = pc => { + // Check Available Ucds and Current UCD when pc.ucd is not null + const avoptions = [] + let currentUcd = null + ucds.forEach(ucd => { + if (usedUcds.indexOf(ucd.value) === -1 || ucd.value === pc.ucd) { + avoptions.push(ucd) + } + if (ucd.value === pc.ucd) { + currentUcd = ucd + } + }) + + const isOptionSelected = pc.ucd !== null + + return ( + + {isOptionSelected ? ( + + onChangeProductContent(pc, null, null)}> + + + + ), + }} + /> + ) : ( + <> + {pc.column_name in editableFields ? ( + + onChangeAlias(pc, e.target.value)} + // onBlur={(e) => { + // console.log("onBlur: ", `${pc.column_name}:${e.target.value}`) + // // console.log(`${pc.column_name}: ${editableFields[pc.column_name]}`) + // // onChangeAlias(pc, editableFields[pc.column_name]) + // }} + // onKeyPress={event => { + // if (event.key === 'Enter') { + // console.log("onKeyPress Enter: ", `${pc.column_name}: ${editableFields[pc.column_name]}`) + // editFieldRefs.current[pc.column_name].blur() + // } + // }} + InputProps={{ + endAdornment: ( + + handleCancelEdit(pc)}> + + + + ) + }} + // inputRef={ref => { + // editFieldRefs.current[pc.column_name] = ref + // }} + autoFocus + /> + + ) : ( + + onSelectUcd(pc, e.target.value)} + // onChange={e => onChangeProductContent(pc, e.target.value)} + > + {avoptions.map(ucd => ( + + {ucd.name} + + ))} + + handleEdit(pc)} + > + + + + )} + + )} + + ) + } + + const createFields = pc => { + return ( + + + + + {createSelect(pc)} + + ) + } + + const catchFormError = data => { + let msg = + 'There was a failure, please try again later, if the problem persists, please contact support.' + if (data.error) { + msg = data.error + } + setFormError(msg) + } + + const handleFormError = () => { + return ( + + {formError} + + ) + } + + return ( + + {isLoading && } + + Please associate the column names of your file with those expected by + the tool. + + + It is okay to leave columns unassociated. + + + Skip this step if the data is not tabular. + + + + {productColumns.map(pc => { + return createFields(pc) + })} + + {formError !== '' && handleFormError()} + + + + + + + ) +} + +NewProductStep3.propTypes = { + productId: PropTypes.number, + onNext: PropTypes.func.isRequired, + onPrev: PropTypes.func.isRequired +} diff --git a/frontend/services/product.js b/frontend/services/product.js index 03ba81c..fdef827 100644 --- a/frontend/services/product.js +++ b/frontend/services/product.js @@ -162,10 +162,11 @@ export const getProductContents = product_id => { .then(res => res.data) } -export const contentAssociation = (pc_id, ucd) => { +export const contentAssociation = (pc_id, ucd, alias) => { return api .patch(`/api/product-contents/${pc_id}/`, { - ucd: ucd === '' ? null : ucd + ucd: ucd === '' ? null : ucd, + alias: alias === '' ? null : alias }) .then(res => res.data) }