diff --git a/client/src/js/directives/findpatient.js b/client/src/js/directives/findpatient.js index 32f2f85bd8..e17c89d06e 100644 --- a/client/src/js/directives/findpatient.js +++ b/client/src/js/directives/findpatient.js @@ -1,316 +1,262 @@ angular.module('bhima.directives') -.directive('findPatient', FindPatientDirective); +.component('bhFindPatient', { + controller: FindPatientComponent, + templateUrl : 'partials/templates/findpatient.tmpl.html', + bindings : { + onSearchComplete : '&', + type : '@' + } +}); -FindPatientDirective.$inject = ['$compile', '$http', 'appcache']; +FindPatientComponent.$inject = ['Patients', 'appcache']; /** -* The FindPatient directive allows a user to serach for a patient by either the -* patient identifier (Project Abbreviation + Reference) or by typeahead on patient -* name. -* -* The typeahead loads data as your type into the input box, pinging th URL -* /patient/search/?name={string} The HTTP endpoints sends back 20 results -* which are then presented to the user. -* -* This Directive take these attributes : -* - type : which take one of these values (inline or panel) -* - on-search-complete : the callback function which get the returned patient -* By default the directive has the `inline` type -* -*/ -function FindPatientDirective($compile, $http, AppCache) { - return { - restrict : 'AE', - templateUrl : 'partials/templates/findpatient.tmpl.html', - scope : { - callback : '&onSearchComplete', - type : '@type' - }, - link : function(scope, element, attrs) { - var cache = new AppCache('FindPatientDirective'), - session = scope.session = {}; - - /** options of search: by ID or Name */ - session.options = [ - { - 'key' : 0, - 'label' : 'FIND.PATIENT_ID', - 'placeholder' : 'FIND.SEARCH_PATIENT_ID' - }, - { - 'key' : 1, - 'label' : 'FIND.PATIENT_NAME', - 'placeholder' : 'FIND.SEARCH_NAME' - } - ]; - - /** default session values */ - session.showSearchView = true; - session.loadStatus = null; - session.validInput = false; - - - /** Expose functions and variables to the template view */ - scope.searchByReference = searchByReference; - scope.searchByName = searchByName; - scope.formatPatient = formatPatient; - scope.selectPatient = selectPatient; - scope.validateNameSearch = validateNameSearch; - scope.submit = submit; - scope.findBy = findBy; - scope.reload = reload; - scope.readInput = readInput; - - /** Starting up the initial setting of the directive */ - startup(); - - - /** - * @function startup - * - * @description setting up the initial parameters of the directive - */ - function startup() { - cache.fetch('option') - .then(loadDefaultOption) - .catch(errorHandler); - } - - /** - * @function searchByReference - * - * @param {string} ref Patient hospital refernce. E.g. HBB123 - * - * @description This function make a call to BHIMA API for finding a patient - * who is identified by a hospital reference. (e.g. HBB123) - */ - function searchByReference(ref) { - session.loadStatus = 'loading'; - - var url = '/patients/search/?reference='; - $http.get(url + ref) - .then(function (response) { - selectPatient(response.data[0]); - }) - .catch(errorHandler) - .finally(); - } - - /** - * @function searchByName - * - * @param {string} text Patient name (first_name, middle_name or last_name) - * - * @description This function make a call to BHIMA API for getting patients - * according the name (first_name, middle_name or last_name). - * - * @return {Array} An array of patients - */ - function searchByName(text) { - session.loadStatus = 'loading'; - - var url = '/patients/search/?name='; - return $http.get(url + text.toLowerCase() + '&limit=20') - .then(function (response) { - response.data.forEach(function (patient) { - patient.label = formatPatient(patient); - }); - return response.data; - }) - .catch(errorHandler) - .finally(); - } - - /** - * @function submit - * - * @description This function is responsible for call the appropriate function - * according we have a search by ID or a search by Name to get data - */ - function submit() { - if (session.selected.key === 0 && session.idInput) { - searchByReference(session.idInput); - - } else if (session.selected.key === 1 && session.nameInput) { - selectPatient(session.nameInput); - } - } - - /** - * @function findBy - * - * @param {object} option The selected option - * - * @description This function is responsible for setting the selected option - * between ID or Name option of search - */ - function findBy(option) { - session.selected = option; - session.loadStatus = null; - session.idInput = undefined; - session.nameInput = undefined; - saveOption(session.selected); - } - - /** - * @function reload - * - * @description This function is responsible for enabling the user to input data - * again for search by showing the inputs zones (search by ID or by name) again. - */ - function reload() { - session.showSearchView = true; - } - - /** - * @function formatPatient - * - * @param {object} patient The patient object - * - * @description This function is responsible for formatting the patient name - * for to be more readable - * - * @returns {string} The formatted patient name - */ - function formatPatient(p) { - return p ? p.first_name + ' ' + p.last_name + ' ' + p.middle_name : ''; - } - - /** - * @function errorHandler - * - * @param {object} error The error object - * - * @description This function is responsible for handling errors which occurs - * and notify the client into the console - */ - function errorHandler(error) { - console.error(error); - } - - /** - * @function selectPatient - * - * @param {object} patient The patient object - * - * @description This function is responsible for handling the result of the search, - * display results and pass the returned patient to the parent controller - */ - function selectPatient(patient) { - session.showSearchView = false; - - if (patient && typeof(patient) === 'object') { - session.loadStatus = 'loaded'; - scope.patient = patient; - - // parse patient metadata - var age = getAge(patient.dob); - patient.age = age.years; - patient.name = formatPatient(patient); - patient.sex = patient.sex.toUpperCase(); - - // call the external $scope callback - scope.callback({ patient : patient }); - - } else { - session.loadStatus = 'error'; - } - - } - - /** - * @function validateNameSearch - * - * @param {string} value The patient reference ID or name - * - * @description Check if the value in the inputs is correct (well defined) - */ - function validateNameSearch(value) { - session.validInput = angular.isDefined(value); - - // Update the nofication - if (!session.validInput) { - session.loadStatus = null; - } - } - - /** - * @function loadDefaultOption - * - * @param {object} option The default option of search - * - * @description This function is responsible for changing the option of search. - * Search by ID or by the name - */ - function loadDefaultOption(option) { - option = option || session.options[0]; - findBy(option); - } - - /** - * @function saveOption - * - * @param {object} option The selected option of search - * - * @description This function is responsible for saving the selected option of - * search in the application cache - */ - function saveOption(option) { - cache.put('option', option); - } - - /** - * @function getAge - * - * @param {date} date A date of a person - * - * @description Get an age object for the patient with years, months, days - * inspired by http://stackoverflow.com/questions/7833709/calculating-age-in-months-and-days - * - * @return {object} The age object - */ - function getAge(date) { - var age = {}, - today = new Date(); - - // convert to date object - date = new Date(date); - - var y = [today.getFullYear(), date.getFullYear()], ydiff = y[0] - y[1], - m = [today.getMonth(), date.getMonth()], mdiff = m[0] - m[1], - d = [today.getDate(), date.getDate()], ddiff = d[0] - d[1]; - - if (mdiff < 0 || (mdiff === 0 && ddiff < 0)) { --ydiff; } - - if (mdiff < 0) { mdiff += 12; } - - if (ddiff < 0) { - date.setMonth(m[1] + 1, 0); - ddiff = date.getDate() - d[1] + d[0]; - --mdiff; - } - - age.years = ydiff > 0 ? ydiff : 0; - age.months = mdiff > 0 ? mdiff : 0; - age.days = ddiff > 0 ? ddiff : 0; - return age; - } - - /** - * @function readInput - * - * @param {object} event An Event object - * - * @description This function capture the "Enter" key push of the user and - * call a function to do something - * - */ - function readInput(event) { - if (event.keyCode === 13) { - submit(); - } - } - + * The Find Patient Component + * + * This component allows a user to serach for a patient by either the + * patient identifier (Project Abbreviation + Reference) or by typeahead on patient + * name. + * + * The typeahead loads data as your type into the input box, pinging th URL + * /patient/search/?name={string} The HTTP endpoints sends back 20 results + * which are presented to the user. + * + * SUPPORTED ATTRIBUTES: + * - type : which take one of these values (inline or panel) (default: inline) + * - on-search-complete : the callback function which get the returned patient + */ +function FindPatientComponent(Patients, AppCache) { + var vm = this; + + /** cache to remember which the search type of the component */ + var cache = new AppCache('FindPatientComponent'); + + /** @const the max number of records to fetch from the server */ + var LIMIT = 20; + + /** supported searches: by name or by id */ + vm.options = [{ + 'key' : 0, + 'label' : 'FIND.PATIENT_ID', + 'placeholder' : 'FIND.SEARCH_PATIENT_ID' + }, { + 'key' : 1, + 'label' : 'FIND.PATIENT_NAME', + 'placeholder' : 'FIND.SEARCH_NAME' + }]; + + vm.timestamp = new Date(); + vm.showSearchView = true; + vm.loadStatus = null; + vm.validInput = false; + + /** Expose functions and variables to the template view */ + vm.searchByReference = searchByReference; + vm.searchByName = searchByName; + vm.formatPatient = formatPatient; + vm.selectPatient = selectPatient; + vm.validateNameSearch = validateNameSearch; + vm.submit = submit; + vm.findBy = findBy; + vm.reload = reload; + vm.readInput = readInput; + + /** fetch the initial setting for the component from appcache */ + cache.fetch('option') + .then(loadDefaultOption); + + /** + * @method searchByReference + * + * @param {string} ref -patient hospital referenece (e.g. HBB123) + * + * @description This function make a call to BHIMA API for finding a patient + * who is identified by a hospital reference. (e.g. HBB123) + */ + function searchByReference(reference) { + vm.loadStatus = 'loading'; + + var options = { + reference : reference, + limit : LIMIT + }; + + // query the patient's search endpoint for the + // reference + Patients.search(options) + .then(function (patients) { + selectPatient(patients[0]); + }) + .catch(handler); + } + + /** + * @method searchByName + * + * @param {string} text Patient name (first_name, middle_name or last_name) + * + * @description This function make a call to BHIMA API for getting patients + * according the name (first_name, middle_name or last_name). + * + * @return {Array} An array of patients + */ + function searchByName(text) { + vm.loadStatus = 'loading'; + + // format query string parameters + var options = { + name : text.toLowerCase(), + limit : LIMIT + }; + + return Patients.search(options) + .then(function (patients) { + + // loop through each + patients.forEach(function (patient) { + patient.label = formatPatient(patient); + }); + + return patients; + }); + } + + /** + * @method submit + * + * @description This function is responsible for call the appropriate function + * according we have a search by ID or a search by Name to get data + */ + function submit() { + if (vm.selected.key === 0 && vm.idInput) { + searchByReference(vm.idInput); + + } else if (vm.selected.key === 1 && vm.nameInput) { + selectPatient(vm.nameInput); + } + } + + /** + * @method findBy + * + * @param {object} option The selected option + * + * @description This function is responsible for setting the selected option + * between ID or Name option of search + */ + function findBy(option) { + vm.selected = option; + vm.loadStatus = null; + vm.idInput = undefined; + vm.nameInput = undefined; + + // save the option for later + cache.put('option', option); + } + + /** + * @method reload + * + * @description This function is responsible for enabling the user to input data + * again for search by showing the inputs zones (search by ID or by name) again. + */ + function reload() { + vm.showSearchView = true; + } + + /** + * @method formatPatient + * + * @param {object} patient The patient object + * + * @description This function is responsible for formatting the patient name + * to be more readable + * + * @returns {string} The formatted patient name + */ + function formatPatient(p) { + return p ? p.first_name + ' ' + p.last_name + ' ' + p.middle_name : ''; + } + + /** + * @method handler + * + * @param {object} error The error object + * + * @description This function is responsible for handling errors which occurs + * and notify the client into the console + */ + function handler(error) { + throw error; + } + + /** + * @method selectPatient + * + * @param {object} patient The patient object + * + * @description This function is responsible for handling the result of the search, + * display results and pass the returned patient to the parent controller + */ + function selectPatient(patient) { + vm.showSearchView = false; + + if (patient && typeof(patient) === 'object') { + vm.loadStatus = 'loaded'; + vm.patient = patient; + + // parse patient metadata + patient.name = formatPatient(patient); + patient.sex = patient.sex.toUpperCase(); + + // call the external function with patient + vm.onSearchComplete({ patient : patient }); + + } else { + vm.loadStatus = 'error'; + } + } + + /** + * @method validateNameSearch + * + * @param {string} value The patient reference ID or name + * + * @description Check if the value in the inputs is correct (well defined) + */ + function validateNameSearch(value) { + vm.validInput = angular.isDefined(value); + + // Update the nofication + if (!vm.validInput) { + vm.loadStatus = null; + } + } + + /** + * @method loadDefaultOption + * + * @param {object} option The default option of search + * + * @description This function is responsible for changing the option of search. + * Search by ID or by the name + */ + function loadDefaultOption(option) { + option = option || vm.options[0]; + findBy(option); + } + + /** + * @method readInput + * + * @param {object} event An Event object + * + * @description This function capture the "Enter" key push of the user and + * call a function to do something + */ + function readInput(event) { + if (event.keyCode === 13) { + submit(); } - }; + } } diff --git a/client/src/js/services/patients.js b/client/src/js/services/patients.js index 39014ceed3..216146c5af 100644 --- a/client/src/js/services/patients.js +++ b/client/src/js/services/patients.js @@ -11,6 +11,7 @@ PatientService.$inject = [ '$http', 'util' ]; */ function PatientService($http, util) { var service = this; + var baseUrl = '/patients/'; service.detail = detail; service.create = create; @@ -19,40 +20,37 @@ function PatientService($http, util) { service.updateGroups = updateGroups; service.logVisit = logVisit; - function detail(uuid) { - var path = '/patients/'; + /** uses the "search" endpoint to pass query strings to the database */ + service.search = search; - return $http.get(path.concat(uuid)) + function detail(uuid) { + return $http.get(baseUrl.concat(uuid)) .then(util.unwrapHttpResponse); } // TODO Service could seperate medical and financial details - depending on form build function create(details) { - var path = '/patients'; - - return $http.post(path, details) + return $http.post(baseUrl, details) .then(util.unwrapHttpResponse); } function update(uuid, definition) { - var path = '/patients/'; - - return $http.put(path.concat(uuid), definition) + return $http.put(baseUrl.concat(uuid), definition) .then(util.unwrapHttpResponse); } // TODO Review/ refactor // Optionally accepts patientUuid - if no uuid is passed this will return all patients groups function groups(patientUuid) { - var path = '/patients/'; + var path; // If a patient ID has been specified - return only the patient groups for that patient if (angular.isDefined(patientUuid)) { - path = path.concat(patientUuid, '/groups'); + path = baseUrl.concat(patientUuid, '/groups'); } else { // No Patient ID is specified - return a list of all patient groups - path = path.concat('groups'); + path = baseUrl.concat('groups'); } return $http.get(path) @@ -60,24 +58,38 @@ function PatientService($http, util) { } function updateGroups(uuid, subscribedGroups) { - var path = '/patients/'; var options = formatGroupOptions(subscribedGroups); + var path = baseUrl.concat(uuid, '/groups'); - path = path.concat(uuid, '/groups'); - - console.log('formatted', options); return $http.post(path, options) .then(util.unwrapHttpResponse); } function logVisit(details) { - var path = '/patients/visit'; + return $http.post(baseUrl.concat('visit'), details) + .then(util.unwrapHttpResponse); + } + + + /** + * Uses query strings to generically search for patients. + * + * @method search + * + * @param {object} options - a JSON of options to be parsed by Angular's + * paramSerializer + */ + function search(options) { + var target = baseUrl.concat('search'); - return $http.post(path, details) + return $http.get(target, { params : options }) .then(util.unwrapHttpResponse); } + + /* ----------------------------------------------------------------- */ + /** Utility Methods */ + /* ----------------------------------------------------------------- */ - // Utility methods function formatGroupOptions(groupFormOptions) { var groupUuids = Object.keys(groupFormOptions); diff --git a/client/src/partials/cash/cash.html b/client/src/partials/cash/cash.html index 3ec8accad8..96a30e7c98 100644 --- a/client/src/partials/cash/cash.html +++ b/client/src/partials/cash/cash.html @@ -35,7 +35,7 @@