diff --git a/.gitignore b/.gitignore
index 2dec24d72c..71ac584da7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,7 @@ nbproject/project.xml
*.sublime-workspace
server/config.js
.brackets.json
-/app/nbproject/private/
\ No newline at end of file
+/app/nbproject/private/
+newrelic_agent.log
+
+newrelic.js
diff --git a/app/helpers/html-line-break.js b/app/helpers/html-line-break.js
new file mode 100644
index 0000000000..6548762c01
--- /dev/null
+++ b/app/helpers/html-line-break.js
@@ -0,0 +1,8 @@
+import Ember from 'ember';
+export default Ember.Helper.helper(function([text]) {
+ if (text !== null && typeof text !== 'undefined') {
+ return new Ember.Handlebars.SafeString(text.replace(/\n/g, '
'));
+ } else {
+ return null;
+ }
+});
\ No newline at end of file
diff --git a/app/initializers/i18n.js b/app/initializers/i18n.js
index 02511aad45..3f727d9ec2 100644
--- a/app/initializers/i18n.js
+++ b/app/initializers/i18n.js
@@ -7,5 +7,6 @@ export default {
app.inject('route', 'i18n', 'service:i18n');
app.inject('controller', 'i18n', 'service:i18n');
app.inject('mixin', 'i18n', 'service:i18n');
+ app.inject('model', 'i18n', 'service:i18n');
}
};
\ No newline at end of file
diff --git a/app/locales/en/translations.js b/app/locales/en/translations.js
index 9c600c99ab..7e994a6d44 100644
--- a/app/locales/en/translations.js
+++ b/app/locales/en/translations.js
@@ -138,12 +138,15 @@ export default {
}
},
labels: {
+ cptcode: 'CPT Code',
loading: 'Loading',
name: 'Name',
patient: 'Patient',
quantity: 'Quantity',
requested_on: 'Requested On',
date: 'Date',
+ date_of_birth: 'Date of Birth',
+ date_of_birth_short: 'DoB',
date_requested: 'Date Requested',
date_completed: 'Date Completed',
description: 'Description',
@@ -155,12 +158,18 @@ export default {
action: 'Action',
notes: 'Notes',
edit: 'Edit',
+ image_orders: 'Image Orders',
+ lab_orders: 'Lab Orders',
+ patient_history: 'Patient History',
imaging_type: 'Imaging Type',
result: 'Result',
results: 'Results',
visit: 'Visit',
+ requests: 'Requests',
+ completed: 'Completed',
+ id: 'Id',
+ on: 'on',
type: 'Type',
- id: 'ID',
sex: 'Sex',
age: 'Age',
username: 'Username',
@@ -196,17 +205,24 @@ export default {
location: 'Location',
provider: 'Provider',
with: 'With',
- all_day: 'All Day'
+ all_day: 'All Day',
+ physician: 'Physician',
+ assisting: 'Assisting',
+ anesthesia: 'Anesthesia',
+ procedures: 'Procedures'
},
messages: {
no_items_found: 'No items found.',
+ no_history_available: 'No history available.',
create_new_record: 'Create a new record?',
create_new_user: 'Create a new user?',
no_users_found: 'No users found.',
are_you_sure_delete: 'Are you sure you wish to delete the user {{user}}?',
user_has_been_saved: 'The user has been saved.',
user_saved: 'User Saved',
+ on_behalf_of: 'on behalf of',
new_patient_has_to_be_created: 'A new patient needs to be created...Please wait..',
+ no_notes_available: 'No additional clinical notes are available for this visit.',
sorry: 'Sorry, something went wrong...'
},
alerts: {
@@ -226,6 +242,7 @@ export default {
delete: 'Delete',
new_user: 'New User',
add_value: 'Add Value',
+ new_note: 'New Note',
import: 'Import',
load_file: 'Load File',
new_request: 'New Request',
@@ -504,6 +521,31 @@ export default {
new_button: '+ new appointment'
}
},
+ visits: {
+ edit: {
+ actions: 'Actions',
+ edit: 'Edit',
+ date: 'Date',
+ authored_by: 'Authored By',
+ note: 'Note',
+ notes: 'Notes',
+ new_note: 'New Note',
+ visit_information: 'Visit Information',
+ new_appointment: 'New Appointment',
+ add_diagnosis: 'Add Diagnosis',
+ diagnosis: 'Diagnosis',
+ delete: 'Delete',
+ procedure: 'Procedure',
+ procedures: 'Procedures',
+ new_procedure: 'New Procedure',
+ labs: 'Labs',
+ new_lab: 'New Lab',
+ imaging: 'Imaging',
+ new_imaging: 'New Imaging',
+ medication: 'Medication',
+ new_medication: 'New Medication'
+ }
+ },
labs: {
section_title: 'Labs',
requests_title: 'Lab Requests',
@@ -530,5 +572,16 @@ export default {
request_saved_title: 'Lab Request Saved',
request_saved_message: 'The lab request has been saved.'
}
+ },
+ common: {
+ actions: 'Actions'
+ },
+ patients: {
+ notes: {
+ on_behalf_of_label: 'On Behalf Of',
+ on_behalf_of_copy: 'on behalf of',
+ please_select_a_visit: 'Please select a visit',
+ note_label: 'Note'
+ }
}
};
diff --git a/app/mixins/patient-notes.js b/app/mixins/patient-notes.js
new file mode 100644
index 0000000000..cb13e8cbfd
--- /dev/null
+++ b/app/mixins/patient-notes.js
@@ -0,0 +1,35 @@
+import Ember from 'ember';
+export default Ember.Mixin.create({
+
+ canAddNote: function() {
+ return this.currentUserCan('add_note') && (!Ember.isEmpty(this.get('visits')) || !Ember.isEmpty(this.get('model.visits')));
+ },
+
+ canDeleteNote: function() {
+ return this.currentUserCan('delete_note');
+ },
+
+ _computeNoteType: function(visit) {
+ switch (visit.get('visitType')) {
+ case 'Admission':
+ if (Ember.isEmpty(visit.get('procedures'))) {
+ return 'Pre-op';
+ } else {
+ return 'Post-op';
+ }
+ break;
+ case 'Clinic':
+ case 'Followup':
+ return 'General';
+ default:
+ return visit.get('visitType');
+ }
+ },
+
+ _setNoteType: function() {
+ var model = this.get('model');
+ if (model.get('noteType') == null) {
+ model.set('noteType', this._computeNoteType(model.get('visit')));
+ }
+ }
+});
\ No newline at end of file
diff --git a/app/mixins/user-session.js b/app/mixins/user-session.js
index be01bed744..faa4e285e8 100644
--- a/app/mixins/user-session.js
+++ b/app/mixins/user-session.js
@@ -437,6 +437,20 @@ export default Ember.Mixin.create({
users: [
'User Administrator',
'System Administrator'
+ ],
+ add_note: [
+ 'Doctor',
+ 'Medical Records Officer',
+ 'Nurse',
+ 'Nurse Manager',
+ 'Patient Administration',
+ 'System Administrator'
+ ],
+ delete_note: [
+ 'Medical Records Officer',
+ 'Nurse Manager',
+ 'Patient Administration',
+ 'System Administrator'
]
},
diff --git a/app/models/patient-note.js b/app/models/patient-note.js
new file mode 100644
index 0000000000..84e3b6c827
--- /dev/null
+++ b/app/models/patient-note.js
@@ -0,0 +1,42 @@
+import AbstractModel from 'hospitalrun/models/abstract';
+import Ember from 'ember';
+import DS from 'ember-data';
+export default AbstractModel.extend({
+ authoredBy: function() {
+ if (!Ember.isEmpty(this.get('attribution'))) {
+ let i18n = this.get('i18n');
+ return this.get('createdBy') + ' ' + i18n.t('patients.notes.on_behalf_of_copy') + ' ' + this.get('attribution');
+ } else {
+ return this.get('createdBy');
+ }
+ }.property('attribution', 'createdBy'),
+ // if the note was written by one person but dictated / given on behalf of another, otherwise, this and createdBy are the same
+ attribution: DS.attr('string'),
+ content: DS.attr('string'),
+ createdBy: DS.attr('string'),
+ date: DS.attr('date'),
+ // custom list of noteTypes of mixins/patient-note-types
+ noteType: DS.attr(),
+ // who is this note about?
+ patient: DS.belongsTo('patient', {
+ async: false
+ }),
+ // if this note is related to a visit, make sure it's noted.
+ visit: DS.belongsTo('visit', {
+ async: false
+ }),
+ validations: {
+ patient: {
+ presence: true
+ },
+ visit: {
+ presence: true
+ },
+ noteType: {
+ presence: true
+ },
+ content: {
+ presence: true
+ }
+ }
+});
\ No newline at end of file
diff --git a/app/models/visit.js b/app/models/visit.js
index 9b26298205..25f1b0f538 100644
--- a/app/models/visit.js
+++ b/app/models/visit.js
@@ -33,7 +33,9 @@ export default AbstractModel.extend({
labs: DS.hasMany('lab', { async: true }),
location: DS.attr('string'),
medication: DS.hasMany('medication', { async: true }),
+ // this field is being deprecated in favor of patient-note
notes: DS.attr('string'),
+ patientNotes: DS.hasMany('patient-note', { async: true }),
outPatient: DS.attr('boolean'),
patient: DS.belongsTo('patient', {
async: false
diff --git a/app/patients/edit/controller.js b/app/patients/edit/controller.js
index b3b38a8b94..5ef22f5d90 100644
--- a/app/patients/edit/controller.js
+++ b/app/patients/edit/controller.js
@@ -1,10 +1,11 @@
import AbstractEditController from 'hospitalrun/controllers/abstract-edit-controller';
import BloodTypes from 'hospitalrun/mixins/blood-types';
import Ember from 'ember';
+import PatientNotes from 'hospitalrun/mixins/patient-notes';
import ReturnTo from 'hospitalrun/mixins/return-to';
import SelectValues from 'hospitalrun/utils/select-values';
import UserSession from 'hospitalrun/mixins/user-session';
-export default AbstractEditController.extend(BloodTypes, ReturnTo, UserSession, {
+export default AbstractEditController.extend(BloodTypes, ReturnTo, UserSession, PatientNotes, {
canAddAppointment: function() {
return this.currentUserCan('add_appointment');
}.property(),
@@ -284,33 +285,41 @@ export default AbstractEditController.extend(BloodTypes, ReturnTo, UserSession,
},
editAppointment: function(appointment) {
- appointment.set('returnToPatient', true);
- appointment.set('returnTo', null);
- this.transitionToRoute('appointments.edit', appointment);
+ if (this.get('canAddAppointment')) {
+ appointment.set('returnToPatient', true);
+ appointment.set('returnTo', null);
+ this.transitionToRoute('appointments.edit', appointment);
+ }
},
editImaging: function(imaging) {
- if (imaging.get('canEdit')) {
- imaging.setProperties({
- 'returnToPatient': true
- });
- this.transitionToRoute('imaging.edit', imaging);
+ if (this.get('canAddImaging')) {
+ if (imaging.get('canEdit')) {
+ imaging.setProperties({
+ 'returnToPatient': true
+ });
+ this.transitionToRoute('imaging.edit', imaging);
+ }
}
},
editLab: function(lab) {
- if (lab.get('canEdit')) {
- lab.setProperties({
- 'returnToPatient': true
- });
- this.transitionToRoute('labs.edit', lab);
+ if (this.get('canAddLab')) {
+ if (lab.get('canEdit')) {
+ lab.setProperties({
+ 'returnToPatient': true
+ });
+ this.transitionToRoute('labs.edit', lab);
+ }
}
},
editMedication: function(medication) {
- if (medication.get('canEdit')) {
- medication.set('returnToPatient', true);
- this.transitionToRoute('medication.edit', medication);
+ if (this.get('canAddMedication')) {
+ if (medication.get('canEdit')) {
+ medication.set('returnToPatient', true);
+ this.transitionToRoute('medication.edit', medication);
+ }
}
},
@@ -319,11 +328,15 @@ export default AbstractEditController.extend(BloodTypes, ReturnTo, UserSession,
},
editProcedure: function(procedure) {
- this.transitionToRoute('procedures.edit', procedure);
+ if (this.get('canAddVisit')) {
+ this.transitionToRoute('procedures.edit', procedure);
+ }
},
editVisit: function(visit) {
- this.transitionToRoute('visits.edit', visit);
+ if (this.get('canAddVisit')) {
+ this.transitionToRoute('visits.edit', visit);
+ }
},
newAppointment: function() {
@@ -358,6 +371,18 @@ export default AbstractEditController.extend(BloodTypes, ReturnTo, UserSession,
});
},
+ showAddPatientNote: function(model) {
+ if (this.get('canAddNote')) {
+ if (Ember.isEmpty(model)) {
+ model = this.get('store').createRecord('patient-note', {
+ patient: this.get('model'),
+ createdBy: this.getUserName()
+ });
+ }
+ this.send('openModal', 'patients.notes', model);
+ }
+ },
+
showDeleteAppointment: function(appointment) {
appointment.set('deleteFromPatient', true);
this.send('openModal', 'appointments.delete', appointment);
diff --git a/app/patients/edit/route.js b/app/patients/edit/route.js
index 6220189d48..0eb7db4f49 100644
--- a/app/patients/edit/route.js
+++ b/app/patients/edit/route.js
@@ -2,14 +2,20 @@ import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route';
import Ember from 'ember';
import PatientId from 'hospitalrun/mixins/patient-id';
import PatientVisits from 'hospitalrun/mixins/patient-visits';
+import PatientNotes from 'hospitalrun/mixins/patient-notes';
import PouchDbMixin from 'hospitalrun/mixins/pouchdb';
-export default AbstractEditRoute.extend(PatientId, PatientVisits, PouchDbMixin, {
+export default AbstractEditRoute.extend(PatientId, PatientVisits, PouchDbMixin, PatientNotes, {
editTitle: 'Edit Patient',
modelName: 'patient',
newTitle: 'New Patient',
photos: null,
actions: {
+ updateNote: function(note) {
+ note.get('visit').save().then(function() {
+ // noop
+ });
+ },
appointmentDeleted: function(model) {
this.controller.send('appointmentDeleted', model);
},
diff --git a/app/patients/edit/template.hbs b/app/patients/edit/template.hbs
index 1296a7970d..95f94bef1f 100644
--- a/app/patients/edit/template.hbs
+++ b/app/patients/edit/template.hbs
@@ -1,21 +1,130 @@
{{#edit-panel editPanelProps=editPanelProps}}
{{#em-form model=model submitButton=false bubbles=false }}
{{#unless isNewOrDeleted}}
- {{patient-summary patient=model visits=model.visits patientProcedures=patientProcedures disablePatientLink=true }}
+ {{patient-summary patient=model visits=model.visits patientProcedures=patientProcedures disablePatientLink=true store=store }}
{{/unless}}
-
+ {{#unless isNewOrDeleted}}
+
+
+ {{#if canAddNote}}
+
+
+
+ {{/if}}
+
+ {{#if model.visits}}
+ {{#each model.visits as |visit|}}
+
+
+
+ {{visit.visitDate}}
+ {{visit.visitType}}
+
+
+ {{#if visit.history}}
+
+
History
+ {{html-line-break visit.history}}
+
+ {{/if}}
+
+ {{#if visit.historySince}}
+
+
History Since
+ {{html-line-break visit.historySince}}
+
+ {{/if}}
+
+ {{#if visit.procedures}}
+
+
{{t 'labels.procedures' }}
+ {{#each visit.procedures as |procedure|}}
+
+
{{date-format procedure.procedureDate}}:
+ {{#if procedure.cptCode}}
+ [{{t 'labels.cptcode' }} - {{procedure.cptCode}}]
+ {{/if}}
+ {{procedure.description}}
+
+
+ {{t 'labels.physician'}}: {{procedure.physician}}
+ {{#if procedure.assistant}}
+ , {{t 'labels.assisting'}}: {{procedure.assistant}}
+ {{/if}}
+ {{#if procedure.anesthesiologist}}
+ , {{t 'labels.anesthesia'}}: {{procedure.anesthesiologist}}
+ {{/if}}
+
+ {{html-line-break procedure.notes}}
+
+ {{/each}}
+
+ {{/if}}
+ {{#if visit.imaging}}
+
+
{{t 'labels.image_orders' }}
+ {{#each visit.imaging as |imaging|}}
+
+
{{date-format imaging.imagingDate}}: {{imaging.imagingType.name}}
+
{{imaging.result}}
+
{{html-line-break imaging.notes}}
+
+ {{/each}}
+
+ {{/if}}
+ {{#if visit.labs}}
+
+
{{t 'labels.lab_orders' }}
+ {{#each visit.labs as |lab|}}
+
+
{{date-format lab.labDate}}: {{lab.labType.name}}
+
{{lab.result}}
+
{{html-line-break lab.notes}}
+
+ {{/each}}
+
+ {{/if}}
+ {{#if visit.patientNotes}}
+
+
{{t 'labels.notes'}}
+ {{#each visit.patientNotes as |note|}}
+
+
{{note.authoredBy}}
+ {{date-format note.date}}[{{note.noteType}}]: {{html-line-break note.content}}
+
+ {{/each}}
+
+ {{else}}
+
+ {{t 'messages.no_notes_available'}}
+
+ {{/if}}
+
+ {{/each}}
+ {{else}}
+ {{t 'messages.no_history_available'}}
+ {{/if}}
+
+
+
+ {{/unless}}
+
-
-
- {{#if canAddMedication}}
-
-
-
- {{/if}}
-
- {{partial 'patients/medication'}}
-
-
-
-
-
- {{#if canAddImaging}}
-
-
-
- {{/if}}
-
- {{partial 'patients/imaging'}}
-
-
-
-
+
- {{#if canAddLab}}
+ {{#if canAddAppointment}}
-
{{/if}}
- {{partial 'patients/labs'}}
+
+
+ {{#each model.appointments as |appointment|}}
+
+ {{appointment.formattedAppointmentDate}} |
+ {{appointment.provider}} |
+ {{appointment.location}} |
+ {{appointment.appointmentType}} |
+ {{appointment.displayStatus}} |
+
+ {{#if canAddAppointment}}
+ {{t 'labels.edit'}}
+ {{/if}}
+ {{#if canDeleteAppointment}}
+
+ Delete
+
+ {{/if}}
+ |
+
+ {{/each}}
+
-
+
{{#if canAddVisit}}
@@ -245,44 +354,46 @@
+
+
+
+ {{#if canAddMedication}}
+
+
+ New Medication
+
+
+ {{/if}}
+
+ {{partial 'patients/medication'}}
+
+
+
+
- {{#if canAddAppointment}}
+ {{#if canAddImaging}}
-
- New Appointment
+
+ New Imaging
{{/if}}
-
-
- {{#each model.appointments as |appointment|}}
-
- {{appointment.formattedAppointmentDate}} |
- {{appointment.provider}} |
- {{appointment.location}} |
- {{appointment.appointmentType}} |
- {{appointment.displayStatus}} |
-
- {{#if canAddAppointment}}
- {{t 'labels.edit'}}
- {{/if}}
- {{#if canDeleteAppointment}}
-
- Delete
-
- {{/if}}
- |
-
- {{/each}}
-
+ {{partial 'patients/imaging'}}
+
+
+
+
+
+ {{#if canAddLab}}
+
+
+ New Lab
+
+
+ {{/if}}
+
+ {{partial 'patients/labs'}}
diff --git a/app/patients/notes/controller.js b/app/patients/notes/controller.js
new file mode 100644
index 0000000000..3425fb1871
--- /dev/null
+++ b/app/patients/notes/controller.js
@@ -0,0 +1,51 @@
+import AbstractEditController from 'hospitalrun/controllers/abstract-edit-controller';
+import Ember from 'ember';
+import IsUpdateDisabled from 'hospitalrun/mixins/is-update-disabled';
+import PatientSubmodule from 'hospitalrun/mixins/patient-submodule';
+import PatientNotes from 'hospitalrun/mixins/patient-notes';
+import UserSession from 'hospitalrun/mixins/user-session';
+export default AbstractEditController.extend(IsUpdateDisabled, UserSession, PatientSubmodule, PatientNotes, {
+ cancelAction: 'closeModal',
+ updateAction: 'updateNote',
+ moduleController: Ember.inject.controller('patients'),
+ physicianList: Ember.computed.alias('moduleController.physicianList'),
+ lookupListsToUpdate: [{
+ name: 'physicianList',
+ property: 'model.attribution',
+ id: 'physician_list'
+ }],
+ title: function() {
+ if (this.get('model.isNew')) {
+ return 'New Note for ' + this.get('model.patient.displayName');
+ } else {
+ return 'Updating Note from ' + (moment(this.get('model.date')).format('MM/DD/YYYY')) + ' for ' + this.get('model.patient.displayName');
+ }
+ }.property('model.patient.displayName'),
+ updateCapability: 'add_note',
+ beforeUpdate: function() {
+ this.set('model.date', new Date());
+ this.set('model.createdBy', this.getUserName());
+ return Ember.RSVP.Promise.resolve();
+ },
+ afterUpdate: function() {
+ this.send(this.get('updateAction'), this.get('model'));
+ this.send(this.get('cancelAction'));
+ },
+ actions: {
+ changeVisit: function() {
+ const selectEl = $('select[name="note-visits"]')[0];
+ const selectedIndex = selectEl.selectedIndex;
+ const content = this.get('patientVisitsForSelect');
+
+ // decrement index by 1 if we have a prompt
+ const contentIndex = selectedIndex - 1;
+
+ const selection = content[contentIndex].selectObject;
+
+ // set the local, shadowed selection to avoid leaking
+ // changes to `selection` out via 2-way binding
+ this.get('model').set('visit', selection);
+ this._setNoteType();
+ }
+ }
+});
\ No newline at end of file
diff --git a/app/patients/notes/template.hbs b/app/patients/notes/template.hbs
new file mode 100644
index 0000000000..361467c33c
--- /dev/null
+++ b/app/patients/notes/template.hbs
@@ -0,0 +1,30 @@
+{{#modal-dialog
+ hideCancelButton=hideCancelButton
+ hideUpdateButton=hideUpdateButton
+ isUpdateDisabled=isUpdateDisabled
+ title=title
+ updateButtonAction=updateButtonAction
+ updateButtonText=updateButtonText }}
+ {{#em-form model=model submitButton=false }}
+ {{em-text label=(t 'patients.notes.note_label') property="content" rows=3 class="test-note-content required form-input-group"}}
+
+
+ {{select-or-typeahead
+ property="attribution"
+ label=(t 'patients.notes.on_behalf_of_label' )
+ list=physicianList
+ selection=attribution
+ className="form-input-group test-note-attribution"
+ }}
+ {{/em-form}}
+{{/modal-dialog}}
\ No newline at end of file
diff --git a/app/styles/_bootstrap-theme.scss b/app/styles/_bootstrap-theme.scss
index c92d40f09a..66403606b4 100644
--- a/app/styles/_bootstrap-theme.scss
+++ b/app/styles/_bootstrap-theme.scss
@@ -285,3 +285,7 @@
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] { margin-left: -18px; }
+
+.form-control {
+ border: 1px solid $blue_lightest2;
+}
diff --git a/app/styles/_bootstrap.scss b/app/styles/_bootstrap.scss
index a12db47b24..507495abf6 100644
--- a/app/styles/_bootstrap.scss
+++ b/app/styles/_bootstrap.scss
@@ -1766,8 +1766,6 @@ output {
background-image: none;
border: none;
border-radius: 3px;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
&:focus {
@@ -4157,7 +4155,6 @@ select[multiple].input-group-sm > {
padding-left: 0;
margin-bottom: 0;
list-style: none;
- font-weight: 300;
font-size: 16px;
> li {
position: relative;
@@ -5674,7 +5671,6 @@ a.list-group-item-danger {
.panel-footer {
padding: 10px 15px;
background-color: #f5f5f5;
- border-top: 1px solid #ddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
diff --git a/app/styles/_variables_mixins.scss b/app/styles/_variables_mixins.scss
index fbd70c90f3..0e7fc2b78f 100644
--- a/app/styles/_variables_mixins.scss
+++ b/app/styles/_variables_mixins.scss
@@ -29,6 +29,7 @@ $sidebar_icon: #92a7bc;
$sidebar_icon_lighter: $navy_light;
$sidebar_sublink_text: #c2d2e3;
$view_sub_nav: #6784a2;
+$content_border: $blue_lightest;
// end variables ------------/
diff --git a/app/styles/app.scss b/app/styles/app.scss
index 90236c3639..e08a4a9aa8 100644
--- a/app/styles/app.scss
+++ b/app/styles/app.scss
@@ -17,6 +17,7 @@
@import 'components/tab_nav';
@import 'components/tab_content';
@import 'components/patient_summary';
+@import 'components/patient_history';
// NOTE: do not place styles in this file
// If you have styles to add, put them in
diff --git a/app/styles/components/_patient_history.scss b/app/styles/components/_patient_history.scss
new file mode 100644
index 0000000000..088513f4bf
--- /dev/null
+++ b/app/styles/components/_patient_history.scss
@@ -0,0 +1,42 @@
+// styles for patient history section
+// viewable at Patients > Edit Patient > History
+.patient-history-item {
+ margin: 15px 0;
+ border-radius: 5px;
+ background: rgba($blue_lightest,.5);
+ padding: 5px 15px 15px;
+
+ .ph-note-item {
+ margin-top: 15px;
+ font-weight: 300;
+ }
+
+ .ph-note-heading {
+ font-size: 16px;
+ font-weight: 600;
+ }
+}
+
+.patient-history-heading {
+ margin: -5px -15px 10px;
+ border-radius: 5px 5px 0 0;
+ background: $blue_lightest;
+ padding: 5px 15px;
+ font-size: 18px;
+
+ .ph-visit-date {
+ display: inline-block;
+ margin: -5px 10px -5px -15px;
+ border-radius: 5px 0 0;
+ background: $blue_lightest2;
+ padding: 5px 15px;
+ line-height: 26px;
+ font-size: 16px;
+ font-weight: 600;
+ }
+
+ .ph-visit-type {
+ display: inline-block;
+ font-weight: 300;
+ }
+}
diff --git a/app/styles/components/_patient_summary.scss b/app/styles/components/_patient_summary.scss
index ba3dc3962d..f228749f30 100644
--- a/app/styles/components/_patient_summary.scss
+++ b/app/styles/components/_patient_summary.scss
@@ -1,18 +1,18 @@
// styles for patient summary
.patient-summary {
position: relative;
- border-bottom: 2px solid $blue_lightest;
+ border-bottom: 2px solid $content_border;
padding-bottom: 15px;
.ps-info-group { margin-bottom: 5px;
&.patient-id {
position: absolute;
- top: 0;
- right: 0;
+ top: -10px;
+ right: -10px;
border-radius: 3px;
- background: $blue_lightest;
- padding: 0 10px;
+ background: rgba($blue_lightest,.7);
+ padding: 10px 20px;
text-align: center;
.ps-info-label {
@@ -24,7 +24,7 @@
.ps-info-data {
font-size: 22px;
- font-weight: 600;
+ font-weight: 300;
}
}
diff --git a/app/styles/components/_sidebar_nav.scss b/app/styles/components/_sidebar_nav.scss
index ba22142657..770a59b672 100644
--- a/app/styles/components/_sidebar_nav.scss
+++ b/app/styles/components/_sidebar_nav.scss
@@ -112,6 +112,7 @@
}
input {
+ border: 0;
border-radius: 5px 0 0 5px;
background-color: $navy_mid;
padding: 6px 12px;
diff --git a/app/styles/components/_tab_nav.scss b/app/styles/components/_tab_nav.scss
index 34e72ab484..82fd57cd9c 100644
--- a/app/styles/components/_tab_nav.scss
+++ b/app/styles/components/_tab_nav.scss
@@ -29,7 +29,7 @@
}
}
- &.tab-nav { border-bottom: 2px solid $blue_lightest;
+ &.tab-nav { border-bottom: 2px solid $content_border;
li {
margin-left: 0;
diff --git a/app/templates/components/patient-summary.hbs b/app/templates/components/patient-summary.hbs
index d38398a9e2..7a87e3948c 100644
--- a/app/templates/components/patient-summary.hbs
+++ b/app/templates/components/patient-summary.hbs
@@ -17,7 +17,7 @@
- {{patient.age}}
+ {{patient.age}} ({{date-format patient.dateOfBirth}})
diff --git a/app/visits/edit/controller.js b/app/visits/edit/controller.js
index 78554bd516..572e86d5d8 100644
--- a/app/visits/edit/controller.js
+++ b/app/visits/edit/controller.js
@@ -1,12 +1,13 @@
import AbstractEditController from 'hospitalrun/controllers/abstract-edit-controller';
import ChargeActions from 'hospitalrun/mixins/charge-actions';
import Ember from 'ember';
+import PatientNotes from 'hospitalrun/mixins/patient-notes';
import PatientSubmodule from 'hospitalrun/mixins/patient-submodule';
import SelectValues from 'hospitalrun/utils/select-values';
import UserSession from 'hospitalrun/mixins/user-session';
import VisitTypes from 'hospitalrun/mixins/visit-types';
-export default AbstractEditController.extend(ChargeActions, PatientSubmodule, UserSession, VisitTypes, {
+export default AbstractEditController.extend(ChargeActions, PatientSubmodule, PatientNotes, UserSession, VisitTypes, {
visitsController: Ember.inject.controller('visits'),
canAddAppointment: function() {
@@ -272,6 +273,18 @@ export default AbstractEditController.extend(ChargeActions, PatientSubmodule, Us
this.send('openModal', 'visits.vitals.edit', newVitals);
},
+ showAddPatientNote: function(model) {
+ if (Ember.isEmpty(model)) {
+ model = this.get('store').createRecord('patient-note', {
+ visit: this.get('model'),
+ createdBy: this.getUserName(),
+ patient: this.get('model').get('patient'),
+ noteType: this._computeNoteType(this.get('model'))
+ });
+ }
+ this.send('openModal', 'patients.notes', model);
+ },
+
newAppointment: function() {
this._addChildObject('appointments.edit');
},
@@ -328,6 +341,24 @@ export default AbstractEditController.extend(ChargeActions, PatientSubmodule, Us
showEditVitals: function(vitals) {
this.send('openModal', 'visits.vitals.edit', vitals);
+ },
+
+ showDeletePatientNote: function(note) {
+ this.send('openModal', 'dialog', Ember.Object.create({
+ confirmAction: 'deletePatientNote',
+ title: 'Delete Note',
+ message: 'Are you sure you want to delete this note?',
+ noteToDelete: note,
+ updateButtonAction: 'confirm',
+ updateButtonText: 'Ok'
+ }));
+ },
+
+ deletePatientNote: function(model) {
+ var note = model.get('noteToDelete');
+ var patientNotes = this.get('model.patientNotes');
+ patientNotes.removeObject(note);
+ this.send('update', true);
}
}
});
diff --git a/app/visits/edit/route.js b/app/visits/edit/route.js
index b650e11fce..ccbe9dfeec 100644
--- a/app/visits/edit/route.js
+++ b/app/visits/edit/route.js
@@ -13,6 +13,14 @@ export default AbstractEditRoute.extend(ChargeRoute, {
startDate: new Date(),
status: 'Admitted'
});
- }
+ },
-});
+ actions: {
+ updateNote: function() {
+ this.controller.send('update', true);
+ },
+ deletePatientNote: function(model) {
+ this.controller.send('deletePatientNote', model);
+ }
+ }
+});
\ No newline at end of file
diff --git a/app/visits/edit/template.hbs b/app/visits/edit/template.hbs
index f21e50e17e..3ab30832fc 100644
--- a/app/visits/edit/template.hbs
+++ b/app/visits/edit/template.hbs
@@ -4,11 +4,11 @@
@@ -60,7 +60,7 @@
{{/if}}
@@ -71,9 +71,9 @@
{{#each model.additionalDiagnoses as |diagnosis|}}
@@ -82,7 +82,7 @@
{{#if canDeleteDiagnosis}}
- Delete
+ {{t 'visits.edit.delete' }}
{{/if}}
|
@@ -92,7 +92,6 @@
{{/if}}
{{em-text label="Patient History" property="history" rows=3 }}
{{em-text label="History since last seen" property="historySince" rows=3 }}
- {{em-text label="Notes" property="notes" rows=3 }}
{{/em-form}}
@@ -102,9 +101,50 @@
+
+
+
+
+ {{#each model.patientNotes as |note|}}
+
+ {{date-format note.date}} |
+ {{note.authoredBy}} |
+ {{note.noteType}}: {{note.content}} |
+
+ {{#if canAddNote}}
+ {{t 'visits.edit.edit'}}
+ {{/if}}
+ {{#if canDeleteNote}}
+
+ {{t 'visits.edit.delete' }}
+
+ {{/if}}
+ |
+
+ {{/each}}
+
+
+
+
+
+
+
@@ -113,9 +153,9 @@
{{#each model.procedures as |procedure|}}
@@ -127,7 +167,7 @@
{{/if}}
{{#if canDeleteProcedure}}
- Delete
+ {{t 'visits.edit.delete' }}
{{/if}}
@@ -141,9 +181,9 @@
@@ -158,10 +198,10 @@
- Labs
+ {{t 'visits.edit.labs' }}
{{#if canAddLab}}
- New Lab
+ {{t 'visits.edit.new_lab' }}
{{/if}}
@@ -177,10 +217,10 @@
- Imaging
+ {{t 'visits.edit.imaging' }}
{{#if canAddImaging}}
- New Imaging
+ {{t 'visits.edit.new_imaging' }}
{{/if}}
diff --git a/config/environment.js b/config/environment.js
index e467a343b0..7a3e2b5948 100644
--- a/config/environment.js
+++ b/config/environment.js
@@ -19,12 +19,14 @@ module.exports = function(environment) {
}
};
+
ENV.contentSecurityPolicy = {
- 'script-src': "'self' 'unsafe-inline' 'unsafe-eval'",
'connect-src': "'self'",
+ 'default-src': "'self'",
'frame-src': "'self'",
- 'style-src': "'self' 'unsafe-inline'",
- 'img-src': "'self' data:"
+ 'img-src': "'self' filesystem: data:",
+ 'script-src': "'self' 'unsafe-inline' 'unsafe-eval'",
+ 'style-src': "'self' 'unsafe-inline'"
};
if (environment === 'test') {
diff --git a/tests/acceptance/appointments-test.js b/tests/acceptance/appointments-test.js
index 488f7eaf3f..3b66d62975 100644
--- a/tests/acceptance/appointments-test.js
+++ b/tests/acceptance/appointments-test.js
@@ -85,6 +85,7 @@ test('Adding a visit to an appointment', function(assert) {
});
click('button:contains(Ok)');
andThen(() => {
+ findWithAssert('button:contains(New Note)');
findWithAssert('button:contains(New Procedure)');
findWithAssert('button:contains(New Medication)');
findWithAssert('button:contains(New Lab)');
diff --git a/tests/acceptance/patient-notes-test.js b/tests/acceptance/patient-notes-test.js
new file mode 100644
index 0000000000..c709667428
--- /dev/null
+++ b/tests/acceptance/patient-notes-test.js
@@ -0,0 +1,106 @@
+import Ember from 'ember';
+import { module, test } from 'qunit';
+import startApp from 'hospitalrun/tests/helpers/start-app';
+
+module('Acceptance | patient notes', {
+ beforeEach: function() {
+ this.application = startApp();
+ },
+
+ afterEach: function() {
+ Ember.run(this.application, 'destroy');
+ }
+});
+
+function tabTest(tabName, tabId) {
+ click(`[data-test-selector=${tabName}]`);
+ andThen(function() {
+ findWithAssert(`#${tabId}`);
+ });
+}
+
+test('patient notes crud testing', function(assert) {
+ runWithPouchDump('default', function() {
+ authenticateUser();
+ visit('/patients');
+ visit('/patients/edit/new');
+ andThen(function() {
+ assert.equal(currentURL(), '/patients/edit/new');
+ });
+ fillIn('.test-first-name input', 'John');
+ fillIn('.test-last-name input', 'Doe');
+ click('.panel-footer button:contains(Add)');
+ waitToAppear('.modal-dialog');
+ andThen(function() {
+ assert.equal(find('.modal-title').text(), 'Patient Saved', 'Patient record has been saved');
+ });
+ click('button:contains(Close)');
+ waitToAppear('.patient-summary');
+ andThen(function() {
+ findWithAssert('.patient-summary');
+ });
+ andThen(function() {
+ tabTest('visits-tab', 'visits');
+ });
+ click('button:contains(New Visit)');
+ andThen(function() {
+ assert.equal(currentURL(), '/visits/edit/new', 'Now in add visiting information route');
+ });
+ click('.panel-footer button:contains(Add)');
+ waitToAppear('.modal-dialog');
+ andThen(() => {
+ assert.equal(find('.modal-title').text(), 'Visit Saved', 'New visit has been saved');
+ });
+ click('button:contains(Ok)');
+ andThen(() => {
+ findWithAssert('button:contains(New Note)');
+ findWithAssert('button:contains(New Procedure)');
+ findWithAssert('button:contains(New Medication)');
+ findWithAssert('button:contains(New Lab)');
+ findWithAssert('button:contains(New Imaging)');
+ findWithAssert('button:contains(New Vitals)');
+ findWithAssert('button:contains(Add Item)');
+ });
+ andThen(function() {
+ assert.equal(find('button:contains(New Note)').length, 1, 'New Note button in visible');
+ click('button:contains(New Note)');
+ });
+ andThen(function() {
+ assert.equal(find('label:contains(Note)').length, 1, 'Notes modal appeared.');
+ });
+ andThen(function() {
+ fillIn('.test-note-content textarea', 'This is a note.');
+ fillIn('.test-note-attribution input', 'Dr. Nick');
+ click('.modal-footer button:contains(Add)');
+ });
+ andThen(function() {
+ waitToAppear('#visit-notes table tr td:contains(This is a note.)');
+ assert.equal(find('#visit-notes table tr td:contains(This is a note.)').length, 1, 'Successfully added note.');
+ });
+ // update note
+ andThen(function() {
+ click('#visit-notes table tr td button:contains(Edit)');
+ waitToAppear('.modal-dialog');
+ });
+ andThen(function() {
+ fillIn('.test-note-content textarea', 'This is an updated note.');
+ click('.modal-footer button:contains(Update)');
+ });
+ andThen(function() {
+ waitToAppear('#visit-notes table tr td:contains(This is an updated note.)');
+ assert.equal(find('#visit-notes table tr td:contains(This is an updated note.)').length, 1, 'Successfully updated note.');
+ });
+ // delete note
+ andThen(function() {
+ waitToAppear('#visit-notes table tr td');
+ click('#visit-notes table tr td button:contains(Delete)');
+ });
+ andThen(function() {
+ waitToAppear('.modal-dialog');
+ click('.modal-footer button:contains(Ok)');
+ });
+ andThen(function() {
+ assert.equal(find('#visit-notes table tr td:contains(This is an updated note.)').length, 0, 'Successfully deleted note.');
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/acceptance/patients-test.js b/tests/acceptance/patients-test.js
index 2bb99d24d6..e7417ede3d 100644
--- a/tests/acceptance/patients-test.js
+++ b/tests/acceptance/patients-test.js
@@ -98,13 +98,6 @@ test('Adding a new patient record', function(assert) {
});
});
-/*function tabTest(tabName, tabTitle) {
- click(`[data-test-selector=${tabName}]`);
- andThen(function() {
- findWithAssert(`[data-test-selector=${tabName}] .active`);
- });
-}*/
-
function testSimpleReportForm(reportName) {
test(`View reports tab | ${reportName} shows start and end dates`, function(assert) {
runWithPouchDump('default', function() {