diff --git a/client/scripts/actions/TravelLogActions.js b/client/scripts/actions/TravelLogActions.js index af196d7f..1de2cfcb 100644 --- a/client/scripts/actions/TravelLogActions.js +++ b/client/scripts/actions/TravelLogActions.js @@ -43,5 +43,13 @@ class _TravelLogActions { deleteEntry(relationshipId) { this.dispatch(relationshipId); } archiveEntry(relationshipId) { this.dispatch(relationshipId); } + + editEntry(relationshipId) { this.dispatch(relationshipId); } + + saveEntry(relationshipId) { this.dispatch(relationshipId); } + + cancelEntry(relationshipId) { this.dispatch(relationshipId); } + + updateEntry(field, value, relationshipId) { this.dispatch({field: field, value: value, relationshipId: relationshipId}); } } export let TravelLogActions = alt.createActions(_TravelLogActions); diff --git a/client/scripts/components/User/TravelLog/Entry.jsx b/client/scripts/components/User/TravelLog/Entry.jsx index 8a5416f0..68c1e8c6 100644 --- a/client/scripts/components/User/TravelLog/Entry.jsx +++ b/client/scripts/components/User/TravelLog/Entry.jsx @@ -21,6 +21,9 @@ import {ProminentButton} from '../../ProminentButton'; import {formatDate} from '../../../formatDate'; import {COIConstants} from '../../../../../COIConstants'; import {TravelLogActions} from '../../../actions/TravelLogActions'; +import TextField from '../TextField'; +import CurrencyField from '../CurrencyField'; +import DateRangeField from '../DateRangeField'; export class Entry extends React.Component { constructor() { @@ -28,6 +31,12 @@ export class Entry extends React.Component { this.deleteEntry = this.deleteEntry.bind(this); this.archiveEntry = this.archiveEntry.bind(this); + this.editEntry = this.editEntry.bind(this); + this.saveEntry = this.saveEntry.bind(this); + this.cancelEntry = this.cancelEntry.bind(this); + this.updateField = this.updateField.bind(this); + this.updateStartDate = this.updateStartDate.bind(this); + this.updateEndDate = this.updateEndDate.bind(this); } deleteEntry() { @@ -38,6 +47,30 @@ export class Entry extends React.Component { TravelLogActions.archiveEntry(this.props.travelLog.relationshipId); } + editEntry() { + TravelLogActions.editEntry(this.props.travelLog.relationshipId); + } + + saveEntry() { + TravelLogActions.saveEntry(this.props.travelLog.relationshipId); + } + + cancelEntry() { + TravelLogActions.cancelEntry(this.props.travelLog.relationshipId); + } + + updateField(evt) { + TravelLogActions.updateEntry(evt.target.id, evt.target.value, this.props.travelLog.relationshipId); + } + + updateStartDate(newValue) { + TravelLogActions.updateEntry('startDate', newValue, this.props.travelLog.relationshipId); + } + + updateEndDate(newValue) { + TravelLogActions.updateEntry('endDate', newValue, this.props.travelLog.relationshipId); + } + render() { let styles = { container: { @@ -92,6 +125,24 @@ export class Entry extends React.Component { }, middle: { marginTop: 10 + }, + textField: { + container: { + display: 'inline-block', + width: '33%' + }, + input: { + padding: '2px 8px', + fontSize: 16, + borderRadius: 5, + border: '1px solid #ccc', + height: 30, + width: '95%' + }, + label: { + marginBottom: 5, + fontWeight: '500' + } } }; @@ -115,7 +166,14 @@ export class Entry extends React.Component { ); } - if (this.props.travelLog.status === COIConstants.RELATIONSHIP_STATUS.DISCLOSED) { + if (this.props.editing === true) { + actionButtons = ( +
+ Save + Cancel +
+ ); + } else if (this.props.travelLog.status === COIConstants.RELATIONSHIP_STATUS.DISCLOSED) { actionButtons = (
{disclosedDate} @@ -125,14 +183,62 @@ export class Entry extends React.Component { } else { actionButtons = (
- Edit + Edit Delete
); } - return ( -
+ let jsx; + if (this.props.editing) { + jsx = ( +
+ + + + + + {actionButtons} +
+ ); + } else { + jsx = (
@@ -162,6 +268,12 @@ export class Entry extends React.Component {
{actionButtons}
+ ); + } + + return ( +
+ {jsx}
); } diff --git a/client/scripts/components/User/TravelLog/TravelLog.jsx b/client/scripts/components/User/TravelLog/TravelLog.jsx index e07ec04f..b1a8209a 100644 --- a/client/scripts/components/User/TravelLog/TravelLog.jsx +++ b/client/scripts/components/User/TravelLog/TravelLog.jsx @@ -36,7 +36,8 @@ export class TravelLog extends React.Component { this.state = { entries: storeState.entries, potentialEntry: storeState.potentialEntry, - validating: storeState.validating + validating: storeState.validating, + entryStates: storeState.entryStates }; this.onChange = this.onChange.bind(this); @@ -56,7 +57,8 @@ export class TravelLog extends React.Component { this.setState({ entries: storeState.entries, potentialEntry: storeState.potentialEntry, - validating: storeState.validating + validating: storeState.validating, + entryStates: storeState.entryStates }); } @@ -94,7 +96,10 @@ export class TravelLog extends React.Component { if (this.state.entries.length > 0) { travelLogs = this.state.entries.map(travelLog => { return ( - + ); }); } diff --git a/client/scripts/stores/TravelLogStore.js b/client/scripts/stores/TravelLogStore.js index ac816362..92282ec3 100644 --- a/client/scripts/stores/TravelLogStore.js +++ b/client/scripts/stores/TravelLogStore.js @@ -21,6 +21,10 @@ import {TravelLogActions} from '../actions/TravelLogActions.js'; import alt from '../alt'; import {processResponse, createRequest} from '../HttpUtils'; +let cloneObject = original => { + return JSON.parse(JSON.stringify(original)); +}; + class _TravelLogStore extends AutoBindingStore { constructor() { super(TravelLogActions); @@ -36,6 +40,7 @@ class _TravelLogStore extends AutoBindingStore { this.sortDirection = 'ASCENDING'; this.filter = 'all'; this.validating = false; + this.entryStates = {}; } refreshTravelLogEntries() { @@ -88,6 +93,54 @@ class _TravelLogStore extends AutoBindingStore { })); } + getEntry(relationshipId) { + return this.entries.find(entry => { + return entry.relationshipId === relationshipId; + }); + } + + editEntry(relationshipId) { + if (!this.entryStates[relationshipId]) { + this.entryStates[relationshipId] = {}; + } + this.entryStates[relationshipId].editing = true; + this.entryStates[relationshipId].snapshot = cloneObject(this.getEntry(relationshipId)); + } + + saveEntry(relationshipId) { + let entryToSave = this.entries.find(entry => { + return entry.relationshipId === relationshipId; + }); + + this.entryStates[relationshipId].editing = false; + this.entryStates[relationshipId].snapshot = undefined; + + createRequest().put('/api/coi/travel-log-entries/' + relationshipId) + .send(entryToSave) + .end(processResponse(() => {})); + } + + cancelEntry(relationshipId) { + let index = this.entries.findIndex(entry => { + return entry.relationshipId === relationshipId; + }); + + if (index >= 0) { + this.entries[index] = this.entryStates[relationshipId].snapshot; + } + + this.entryStates[relationshipId].editing = false; + this.entryStates[relationshipId].snapshot = undefined; + } + + updateEntry(data) { + let entryToSave = this.entries.find(entry => { + return entry.relationshipId === data.relationshipId; + }); + + entryToSave[data.field] = data.value; + } + turnOnValidations() { this.validating = true; } diff --git a/server/db/TravelLogDB.js b/server/db/TravelLogDB.js index 6062651e..c52775f4 100644 --- a/server/db/TravelLogDB.js +++ b/server/db/TravelLogDB.js @@ -19,6 +19,7 @@ /*eslint camelcase:0 */ import {COIConstants} from '../../COIConstants'; import {verifyRelationshipIsUsers} from './CommonDB'; +import * as FileService from '../services/fileService/FileService'; let getKnex; try { @@ -149,13 +150,18 @@ let handleTravelLogEntry = (trx, disclosureId, entry, status) => { }); }; +let getAnnualDisclosureForUser = (trx, schoolId) => { + return trx('disclosure').select('status_cd', 'id').where({ + user_id: schoolId, + type_cd: COIConstants.DISCLOSURE_TYPE.ANNUAL + }); +}; + export let createTravelLogEntry = (dbInfo, entry, userInfo) => { let knex = getKnex(dbInfo); return knex.transaction(trx => { - return trx('disclosure').select('status_cd', 'id').where({ - user_id: userInfo.schoolId, - type_cd: COIConstants.DISCLOSURE_TYPE.ANNUAL - }).then(disclosure => { + return getAnnualDisclosureForUser(trx, userInfo.schoolId) + .then(disclosure => { if (disclosure[0]) { if (isSubmitted(disclosure[0].status_cd) === true) { return handleTravelLogEntry(trx, disclosure[0].id, entry, COIConstants.RELATIONSHIP_STATUS.PENDING); @@ -194,13 +200,96 @@ let deleteRelationship = (trx, id) => { .where('id', id); }; +let getQuestionnaireAnswerIds = (trx, id) => { + return trx('fin_entity_answer') + .select('questionnaire_answer_id') + .where('fin_entity_id', id) + .then(answers => { + return answers.map(answer => { + return answer.questionnaire_answer_id; + }); + }); +}; + +let deleteFinEntityAnswers = (trx, id) => { + return trx('fin_entity_answer') + .del() + .where('fin_entity_id', id); +}; + +let deleteQuestionnaireAnswers = (trx, answerIds) => { + return trx('questionnaire_answer') + .del() + .whereIn('id', answerIds); +}; + +let deleteEntityAnswers = (trx, id) => { + return getQuestionnaireAnswerIds(trx, id).then(answerIds => { + return deleteFinEntityAnswers(trx, id).then(() => { + return deleteQuestionnaireAnswers(trx, answerIds); + }); + }); +}; + +let getEntityFiles = (trx, id) => { + return trx('file') + .select('id', 'key') + .where({ + ref_id: id, + file_type: COIConstants.FILE_TYPE.FINANCIAL_ENTITY + }); +}; + +let deleteDbFiles = (trx, id) => { + return trx('file') + .del() + .where({ + ref_id: id, + file_type: COIConstants.FILE_TYPE.FINANCIAL_ENTITY + }); +}; + +let deleteFileData = (files) =>{ + return Promise.all( + files.map(file => { + return new Promise((resolve, reject) => { + FileService.deleteFile(file.key, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }) + ); +}; + +let deleteEntityFiles = (trx, id) => { + return getEntityFiles(trx, id).then(files=> { + return deleteDbFiles(trx, id).then(() => { + return deleteFileData(files); + }); + }); +}; + +let deleteEntity = (trx, id) => { + return trx('fin_entity') + .del() + .where('id', id); +}; + let deleteEntityIfAllRelationshipsAreDelete = (trx, entityId) => { return trx('relationship') - .select('') + .select('id') .where('fin_entity_id', entityId) .then(rows => { if (!rows.length) { - return trx('fin_entity').del().where('id', entityId); + return deleteEntityAnswers(trx, entityId).then(() => { + return deleteEntityFiles(trx, entityId).then(() => { + return deleteEntity(trx, entityId); + }); + }); } }); }; @@ -280,6 +369,72 @@ let updateRelationship = (trx, entry, id) => { } }; +let handleOldEntity = (trx, entityId) => { + return deleteEntityIfAllRelationshipsAreDelete(trx, entityId); +}; + +let getEntityNameFromId = (trx, id) => { + return trx('fin_entity') + .select('name') + .where('id', id) + .then(entity => { + return entity[0].name; + }); +}; + +let getEntityIdFromName = (trx, name, disclosureId) => { + return trx('fin_entity') + .select('id') + .where({ + name: name, + disclosure_id: disclosureId + }) + .then(entity => { + if (entity[0]) { + return entity[0].id; + } else { + return undefined; + } + }); +}; + +let getRelationship = (trx, id) => { + return trx('relationship') + .select('fin_entity_id') + .where('id', id); +}; + +let updateRelationshipEntityId = (trx, id, entityId) => { + return trx('relationship') + .update({fin_entity_id: entityId}) + .where('id', id); +}; + +let updateEntity = (trx, entry, id, schoolId) => { + return getRelationship(trx, id).then(relationship => { + return getEntityNameFromId(trx, relationship[0].fin_entity_id).then(entityName => { + if (entry.entityName === entityName) { + return undefined; + } else { + return getAnnualDisclosureForUser(trx, schoolId).then(disclosure => { + return getEntityIdFromName(trx, entry.entityName, disclosure[0].id).then(entityId => { + if (entityId) { + return updateRelationshipEntityId(trx, id, entityId).then(()=> { + return handleOldEntity(trx, relationship[0].fin_entity_id); + }); + } else { + return createNewEntity(trx, disclosure[0].id, entry, COIConstants.RELATIONSHIP_STATUS.IN_PROGRESS).then(newEntityId => { + return updateRelationshipEntityId(trx, id, newEntityId).then(()=> { + return handleOldEntity(trx, relationship[0].fin_entity_id); + }); + }); + } + }); + }); + } + }); + }); +}; export let updateTravelLogEntry = (dbInfo, entry, id, userInfo) => { let knex = getKnex(dbInfo); @@ -289,7 +444,8 @@ export let updateTravelLogEntry = (dbInfo, entry, id, userInfo) => { return knex.transaction(trx=>{ return Promise.all([ updateTravelRelationship(trx, entry, id), - updateRelationship(trx, entry, id) + updateRelationship(trx, entry, id), + updateEntity(trx, entry, id, userInfo.schoolId) ]).then(() => { return entry; });