Skip to content

Commit

Permalink
Merge pull request #1366 from alliance-genome/SCRUM-3354-alt
Browse files Browse the repository at this point in the history
SCRUM-3354
  • Loading branch information
adamgibs authored Dec 12, 2023
2 parents 555c5c1 + a05da77 commit ddfdfe7
Show file tree
Hide file tree
Showing 12 changed files with 848 additions and 60 deletions.
48 changes: 48 additions & 0 deletions src/main/cliapp/src/components/Editors/EvidenceCodeEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { AutocompleteEditor } from "../Autocomplete/AutocompleteEditor";
import { buildAutocompleteFilter, autocompleteSearch } from "../../utils/utils";
import { SearchService } from "../../service/SearchService";
import { EvidenceAutocompleteTemplate } from "../Autocomplete/EvidenceAutocompleteTemplate";
import { ErrorMessageComponent } from "../Error/ErrorMessageComponent";

const evidenceSearch = (event, setFiltered, setInputValue) => {
const searchService = new SearchService();
const autocompleteFields = ["curie", "name", "abbreviation"];
const endpoint = "ecoterm";
const filterName = "evidenceFilter";
const filter = buildAutocompleteFilter(event, autocompleteFields);
const otherFilters = {
obsoleteFilter: {
"obsolete": {
queryString: false
}
},
subsetFilter: {
"subsets": {
queryString: "agr_eco_terms"
}
}
};

setInputValue(event.query);
autocompleteSearch(searchService, endpoint, filterName, filter, setFiltered, otherFilters);
};

export const EvidenceCodeEditor = ({ props, errorMessages, onChangeHandler }) => {
return (
<>
<AutocompleteEditor
search={evidenceSearch}
initialValue={props.rowData.evidenceCode}
rowProps={props}
fieldName='evidenceCode'
valueDisplay={(item, setAutocompleteHoverItem, op, query) =>
<EvidenceAutocompleteTemplate item={item} setAutocompleteHoverItem={setAutocompleteHoverItem} op={op} query={query} />}
onValueChangeHandler={onChangeHandler}
/>
<ErrorMessageComponent
errorMessages={errorMessages[props.rowIndex]}
errorField="evidenceCode"
/>
</>
);
};
40 changes: 40 additions & 0 deletions src/main/cliapp/src/components/Editors/GeneEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { AutocompleteEditor } from '../Autocomplete/AutocompleteEditor';
import { SearchService } from '../../service/SearchService';
import { autocompleteSearch, buildAutocompleteFilter } from '../../utils/utils';
import { LiteratureAutocompleteTemplate } from '../Autocomplete/LiteratureAutocompleteTemplate';
import { DialogErrorMessageComponent } from "../Error/DialogErrorMessageComponent";

const geneSearch = (event, setFiltered, setInputValue) => {
const searchService = new SearchService();
const autocompleteFields = [
"curie", "crossReferences.referencedCurie", "geneFullName.formatText", "geneFullName.displayText",
"geneSymbol.formatText", "geneSymbol.displayText", "geneSynonyms.formatText", "geneSynonyms.displayText",
"geneSystematicName.formatText", "geneSystematicName.displayText", "geneSecondaryIds.secondaryId"
];
const endpoint = "gene";
const filterName = "objectFilter";
const filter = buildAutocompleteFilter(event, autocompleteFields);

setInputValue(event.query);
autocompleteSearch(searchService, endpoint, filterName, filter, setFiltered);
}

export const GeneEditor = ({ props, errorMessages, onChange }) => {
return (
<>
<AutocompleteEditor
search={geneSearch}
initialValue={props?.rowData?.objectGene?.curie}
rowProps={props}
fieldName='objectGene'
valueDisplay={(item, setAutocompleteHoverItem, op, query) =>
<LiteratureAutocompleteTemplate item={item} setAutocompleteHoverItem={setAutocompleteHoverItem} op={op} query={query}/>}
onValueChangeHandler={onChange}
/>
<DialogErrorMessageComponent
errorMessages={errorMessages[props?.rowIndex]}
errorField={"objectGene"}
/>
</>
);
};
57 changes: 57 additions & 0 deletions src/main/cliapp/src/components/Editors/RelatedNoteEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useRef } from 'react';
import { ErrorMessageComponent } from '../Error/ErrorMessageComponent';
import { Button } from 'primereact/button';
import { EditMessageTooltip } from '../EditMessageTooltip';

export const RelatedNoteEditor = ({ rowProps, relatedNote, errorMessages, setRelatedNotesData }) => {
const errorMessagesRef = useRef();
errorMessagesRef.current = errorMessages;

const handleRelatedNotesOpenInEdit = (event, rows, rowIndex) => {
event.preventDefault();
const index = rowIndex % rows;
let _relatedNotesData = {};
_relatedNotesData["originalRelatedNotes"] = relatedNote ? [relatedNote] : undefined;
_relatedNotesData["dialogIsVisible"] = true;
_relatedNotesData["rowIndex"] = index;
_relatedNotesData["errorMessages"] = errorMessages;
_relatedNotesData["rowProps"] = rowProps;
setRelatedNotesData(() => ({
..._relatedNotesData
}));
};

if (relatedNote) {
return (
<>
<div>
<Button className="p-button-text"
onClick={(event) => { handleRelatedNotesOpenInEdit(event, rowProps.props.rows, rowProps.rowIndex); }} >
<span style={{ textDecoration: 'underline' }}>
{relatedNote.freeText}
<i className="pi pi-user-edit" style={{ 'fontSize': '1em' }}></i>
</span>&nbsp;&nbsp;&nbsp;&nbsp;
<EditMessageTooltip object="allele" />
</Button>
</div>
<ErrorMessageComponent errorMessages={errorMessagesRef.current[rowProps.rowIndex]} errorField={"relatedNote"} style={{ 'fontSize': '1em' }} />
</>
);
} else {
return (
<>
<div>
<Button className="p-button-text"
onClick={(event) => { handleRelatedNotesOpenInEdit(event, rowProps.props.rows, rowProps.rowIndex); }} >
<span style={{ textDecoration: 'underline' }}>
Add Note
<i className="pi pi-user-edit" style={{ 'fontSize': '1em' }}></i>
</span>&nbsp;&nbsp;&nbsp;&nbsp;
<EditMessageTooltip />
</Button>
</div>
<ErrorMessageComponent errorMessages={errorMessagesRef.current[rowProps.rowIndex]} errorField={"relatedNote"} style={{ 'fontSize': '1em' }} />
</>
);
}
};
244 changes: 244 additions & 0 deletions src/main/cliapp/src/components/RelatedNotesDialogEditOnly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import React, { useRef, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { ColumnGroup } from 'primereact/columngroup';
import { Row } from 'primereact/row';
import { DeleteAction } from './Actions/DeletionAction';
import { InputTextAreaEditor } from './InputTextAreaEditor';
import { DialogErrorMessageComponent } from './Error/DialogErrorMessageComponent';
import { InternalEditor } from './Editors/InternalEditor';
import { useVocabularyTermSetService } from '../service/useVocabularyTermSetService';
import { ValidationService } from '../service/ValidationService';
import { ControlledVocabularyDropdown } from './ControlledVocabularySelector';
import { validate } from '../utils/utils';

export const RelatedNotesDialogEditOnly = ({
relatedNotesData,
errorMessagesMainRow,
dispatch,
setRelatedNotesData,
singleValue = false,
onChange,
errorField = "relatedNotes"
}) => {
const {
originalRelatedNotes,
dialogIsVisible,
rowIndex,
} = relatedNotesData;
const [errorMessages, setErrorMessages] = useState([]);
const validationService = new ValidationService();
const tableRef = useRef(null);
const [editingRows, setEditingRows] = useState({});
const noteTypeTerms = useVocabularyTermSetService("allele_genomic_entity_association_note_type");
const [localRelatedNotes, setLocalRelatedNotes] = useState([]);

const cloneNotes = (clonableNotes) => {
if (!clonableNotes) return [{ dataKey: 0 }];
let _clonableNotes = global.structuredClone(clonableNotes);
_clonableNotes.forEach((note, index) => {
note.dataKey = index;
});
return _clonableNotes;
};

const showDialogHandler = () => {
let _localRelatedNotes = cloneNotes(originalRelatedNotes);
setLocalRelatedNotes(_localRelatedNotes);

let rowsObject = {};
_localRelatedNotes.forEach((note) => {
rowsObject[`${note.dataKey}`] = true;
});
setEditingRows(rowsObject);
};

const deletionHandler = (e, index) => {
e.preventDefault();
let _localRelatedNotes = global.structuredClone(localRelatedNotes);
_localRelatedNotes.splice(index, 1);
let _errorMessages = global.structuredClone(errorMessages);
_errorMessages.splice(index, 1);
setLocalRelatedNotes(_localRelatedNotes);
setErrorMessages(_errorMessages);
};

const updateLocalRelatedNotes = (index, field, value) => {
const _localRelatedNotes = global.structuredClone(localRelatedNotes);
_localRelatedNotes[index][field] = value;
setLocalRelatedNotes(_localRelatedNotes);
};
const onNoteTypeEditorValueChange = (props, event) => {
props.editorCallback(event.target.value);
updateLocalRelatedNotes(props.rowIndex, "noteType", event.target.value);
};

const noteTypeEditor = (props) => {
return (
<>
<ControlledVocabularyDropdown
field="noteType"
options={noteTypeTerms.sort((a, b) => (a.name > b.name) ? 1 : -1)}
editorChange={onNoteTypeEditorValueChange}
props={props}
showClear={false}
dataKey='id'
/>
<DialogErrorMessageComponent errorMessages={errorMessages[props.rowIndex]} errorField={"noteType"} />
</>
);
};

const onFreeTextEditorValueChange = (event, props) => {
props.editorCallback(event.target.value);
updateLocalRelatedNotes(props.rowIndex, "freeText", event.target.value);
};

const freeTextEditor = (props, fieldName, errorMessages) => {
return (
<>
<InputTextAreaEditor
initalValue={props.value}
editorChange={(e) => onFreeTextEditorValueChange(e, props)}
rows={5}
columns={30}
/>
<DialogErrorMessageComponent errorMessages={errorMessages[props.rowIndex]} errorField={fieldName} />
</>
);
};

const internalOnChangeHandler = (props, event) => {
props.editorCallback(event.target.value);
updateLocalRelatedNotes(props.rowIndex, "internal", event.target.value.name);
};

const validateTable = async () => {
const results = await validate(localRelatedNotes, 'note', validationService);
const errors = [];
let anyErrors = false;
results.forEach((result, index) => {
const { isError, data } = result;
if (isError) {
errors[index] = {};
if (!data) return;
Object.keys(data).forEach((field) => {
errors[index][field] = {
severity: "error",
message: data[field]
};
});
anyErrors = true;
}
});
setErrorMessages(errors);
return anyErrors;
};

const saveDataHandler = async () => {
setErrorMessages([]);
const anyErrors = await validateTable();
if (anyErrors) return;
onChange(rowIndex, localRelatedNotes, relatedNotesData.rowProps);

const errorMessagesCopy = global.structuredClone(errorMessagesMainRow);
let messageObject = {
severity: "warn",
message: "Pending Edits!"
};
errorMessagesCopy[rowIndex] = {};
errorMessagesCopy[rowIndex][errorField] = messageObject;
dispatch({ type: "UPDATE_TABLE_ERROR_MESSAGES", entityType: "alleleGeneAssociations", errorMessages: errorMessagesCopy });
setRelatedNotesData((relatedNotesData) => {
return {
...relatedNotesData,
dialogIsVisible: false,
};
});
setLocalRelatedNotes([]);
};

const createNewNoteHandler = () => {
let cnt = localRelatedNotes ? localRelatedNotes.length : 0;
localRelatedNotes.push({
dataKey: cnt,
noteType: {
name: ""
},
internal: false
});
let _editingRows = { ...editingRows, ...{ [`${cnt}`]: true } };
setEditingRows(_editingRows);
};

const hideDialog = () => {
setErrorMessages([]);
setRelatedNotesData((relatedNotesData) => {
return {
...relatedNotesData,
dialogIsVisible: false,
};
});
let _localRelatedNotes = [];
setLocalRelatedNotes(_localRelatedNotes);
};

const footerTemplate = () => {
return (
<div>
<Button label="Cancel" icon="pi pi-times" onClick={hideDialog} className="p-button-text" />
<Button label="New Note" icon="pi pi-plus" onClick={createNewNoteHandler}
disabled={singleValue && localRelatedNotes.length > 0} />
<Button label="Keep Edits" icon="pi pi-check" onClick={saveDataHandler} />
</div>
);
};

let headerGroup = <ColumnGroup>
<Row>
<Column header="Actions" />
<Column header="Note Type" />
<Column header="Text" />
<Column header="Internal" />
</Row>
</ColumnGroup>;

const onRowEditChange = (e) => {
return null;
};


return (
<div>
<Dialog visible={dialogIsVisible} className='w-8' modal onHide={hideDialog} closable onShow={showDialogHandler} footer={footerTemplate}>
<h3>Related Notes</h3>
<DataTable value={localRelatedNotes} dataKey="dataKey" showGridlines editMode='row' headerColumnGroup={headerGroup}
editingRows={editingRows} onRowEditChange={onRowEditChange} ref={tableRef}>
<Column editor={(props) => <DeleteAction deletionHandler={deletionHandler} index={props.rowIndex} />}
className='max-w-4rem' bodyClassName="text-center" headerClassName='surface-0' frozen />
<Column editor={noteTypeEditor} field="noteType.name" header="Note Type" headerClassName='surface-0' />
<Column
editor={(props) => freeTextEditor(props, "freeText", errorMessages)}
field="freeText"
header="Text"
headerClassName='surface-0'
className='wrap-word max-w-35rem'
/>
<Column
editor={(props) => {
return <InternalEditor
props={props}
errorMessages={errorMessages}
internalOnChangeHandler={internalOnChangeHandler}
rowIndex={props.rowData.rowIndex}
/>;
}}
field="internal" header="Internal" headerClassName='surface-0' />
</DataTable>
</Dialog>
</div>
);

};
Loading

0 comments on commit ddfdfe7

Please sign in to comment.