├── app ├── components │ ├── .gitkeep │ ├── patient-detail.js │ ├── stats-card.js │ ├── patient-listing.js │ ├── encounter-listing.js │ ├── observation-listing.js │ ├── nav-titlebar.js │ ├── table-listing.js │ ├── placeholder-chart.js │ ├── nav-pagination.js │ └── donut-chart.js ├── helpers │ ├── .gitkeep │ ├── format-navtitle.js │ ├── phone-preference.js │ ├── address-country.js │ ├── language-preference.js │ ├── official-name.js │ ├── medrecord-number.js │ ├── passport-number.js │ ├── format-ssn.js │ ├── drivers-license.js │ └── address-line1.js ├── models │ ├── .gitkeep │ ├── change.js │ ├── encounter.js │ ├── observation.js │ └── patient.js ├── routes │ ├── .gitkeep │ ├── about.js │ ├── index.js │ ├── dashboard.js │ ├── tables.js │ ├── patients.js │ ├── encounters.js │ └── observations.js ├── controllers │ ├── .gitkeep │ ├── patients.js │ ├── tables.js │ ├── dashboard.js │ ├── encounters.js │ └── observations.js ├── templates │ ├── components │ │ ├── .gitkeep │ │ ├── placeholder-chart.hbs │ │ ├── stats-card.hbs │ │ ├── table-listing.hbs │ │ ├── donut-chart.hbs │ │ ├── patient-listing.hbs │ │ ├── encounter-listing.hbs │ │ ├── observation-listing.hbs │ │ ├── nav-titlebar.hbs │ │ ├── nav-pagination.hbs │ │ └── patient-detail.hbs │ ├── index.hbs │ ├── encounters.hbs │ ├── patients.hbs │ ├── tables.hbs │ ├── observations.hbs │ ├── dashboard.hbs │ ├── tables │ │ └── index.hbs │ ├── patients │ │ ├── index.hbs │ │ └── patient.hbs │ ├── encounters │ │ ├── index.hbs │ │ └── encounter.hbs │ ├── observations │ │ ├── index.hbs │ │ └── observation.hbs │ ├── dashboard │ │ └── index.hbs │ ├── application.hbs │ └── about.hbs ├── resolver.js ├── serializers │ └── application.js ├── adapters │ └── application.js ├── styles │ └── app.scss ├── app.js ├── index.html └── router.js ├── .watchmanconfig ├── .firebaserc ├── public ├── robots.txt └── assets │ └── images │ └── benjamin-deyoung-1437086-unsplash.jpg ├── jsconfig.json ├── config ├── optional-features.json ├── targets.js └── environment.js ├── firebase.json ├── .eslintignore ├── .ember-cli.js ├── .editorconfig ├── .gitignore ├── ember-cli-build.js ├── .eslintrc.js └── package.json /app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "fhir-admin" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; -------------------------------------------------------------------------------- /app/templates/encounters.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 | -------------------------------------------------------------------------------- /app/templates/patients.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 | -------------------------------------------------------------------------------- /app/templates/tables.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 | -------------------------------------------------------------------------------- /app/templates/observations.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 | -------------------------------------------------------------------------------- /app/templates/dashboard.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 | -------------------------------------------------------------------------------- /app/routes/about.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/templates/tables/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/patients/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/encounters/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/encounters/encounter.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /app/components/patient-detail.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/components/stats-card.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | 5 | }); 6 | -------------------------------------------------------------------------------- /app/templates/observations/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/observations/observation.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /app/templates/patients/patient.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | }, 5 | "exclude": ["node_modules", "dist"] 6 | } 7 | -------------------------------------------------------------------------------- /app/serializers/application.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.JSONAPISerializer.extend({ 4 | keyForAttribute(key){ return key; } 5 | }); 6 | -------------------------------------------------------------------------------- /config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "jquery-integration": false, 4 | "template-only-glimmer-components": true 5 | } 6 | -------------------------------------------------------------------------------- /public/assets/images/benjamin-deyoung-1437086-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patterns/fhiradmin/master/public/assets/images/benjamin-deyoung-1437086-unsplash.jpg -------------------------------------------------------------------------------- /app/models/change.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | resourceType: DS.attr(), 5 | count: DS.attr(), 6 | last: DS.attr() 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | beforeModel() { 5 | this.replaceWith('dashboard'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.JSONAPIAdapter.extend({ 4 | namespace: 'api/v1', 5 | host: 'http://localhost:4000' 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /app/controllers/patients.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['offset', 'limit', 'sort'], 5 | offset: 1, 6 | limit: 20, 7 | sort: "-ts" 8 | }); 9 | -------------------------------------------------------------------------------- /app/controllers/tables.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['offset', 'limit', 'sort'], 5 | offset: 1, 6 | limit: 20, 7 | sort: "-count" 8 | }); 9 | -------------------------------------------------------------------------------- /app/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['offset', 'limit', 'sort'], 5 | offset: 1, 6 | limit: 5, 7 | sort: "-count" 8 | }); 9 | -------------------------------------------------------------------------------- /app/controllers/encounters.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['offset', 'limit', 'sort'], 5 | offset: 1, 6 | limit: 40, 7 | sort: "-ts" 8 | }); 9 | -------------------------------------------------------------------------------- /app/controllers/observations.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['offset', 'limit', 'sort'], 5 | offset: 1, 6 | limit: 40, 7 | sort: "-ts" 8 | }); 9 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/components/patient-listing.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default Component.extend({ 5 | router: service(), 6 | 7 | actions: { 8 | zoom(pid) { 9 | this.get('router').transitionTo('patients.patient', pid); 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /bower.json.ember-try 20 | /package.json.ember-try 21 | -------------------------------------------------------------------------------- /app/components/encounter-listing.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default Component.extend({ 5 | router: service(), 6 | 7 | actions: { 8 | zoom(eid) { 9 | this.get('router').transitionTo('encounters.encounter', eid); 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/models/encounter.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | class: DS.attr(), 5 | extension: DS.attr(), 6 | meta: DS.attr(), 7 | period: DS.attr(), 8 | serviceProvider: DS.attr(), 9 | status: DS.attr(), 10 | resourceType: DS.attr(), 11 | subject: DS.attr(), 12 | type: DS.attr() 13 | }); 14 | -------------------------------------------------------------------------------- /app/components/observation-listing.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default Component.extend({ 5 | router: service(), 6 | 7 | actions: { 8 | zoom(eid) { 9 | this.get('router').transitionTo('observations.observation', eid); 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/templates/components/placeholder-chart.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#each @segments as |seg index|}} 3 | {{#if (gt seg.count 0)}} 4 | 5 | 8 | 9 | {{/if}} 10 | {{/each}} 11 | 12 | -------------------------------------------------------------------------------- /app/models/observation.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | category: DS.attr(), 5 | code: DS.attr(), 6 | context: DS.attr(), 7 | meta: DS.attr(), 8 | effective: DS.attr(), 9 | issued: DS.attr(), 10 | status: DS.attr(), 11 | resourceType: DS.attr(), 12 | subject: DS.attr(), 13 | value: DS.attr() 14 | }); 15 | -------------------------------------------------------------------------------- /app/templates/components/stats-card.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each @segments as |seg|}} 3 | {{#if (gt seg.count 0)}} 4 |
5 |
{{seg.count}}
6 |

{{seg.resourceType}}

updates 7 |
8 | {{/if}} 9 | {{/each}} 10 |
11 | -------------------------------------------------------------------------------- /app/components/nav-titlebar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | import { computed } from '@ember/object'; 4 | 5 | export default Component.extend({ 6 | router: service(), 7 | 8 | routeName: computed('router.currentRouteName', function() { 9 | return this.router.currentRouteName; 10 | }) 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /app/templates/dashboard/index.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /.ember-cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.EMBER_VERSION = "OCTANE"; 4 | 5 | module.exports = { 6 | /** 7 | Ember CLI sends analytics information by default. The data is completely 8 | anonymous, but there are times when you might want to disable this behavior. 9 | 10 | Setting `disableAnalytics` to true will prevent any data from being sent. 11 | */ 12 | "disableAnalytics": false 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/app.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary: #650360; 3 | $link: #650360; 4 | $info: #00A4D9; 5 | $success: #5eadb2; 6 | $warning: #fce473; 7 | $danger: #ed6c63; 8 | $background: #cccccc; 9 | $body-background-color: #777777; 10 | $navbar-background-color: #777777; 11 | $table-background-color: #999999; 12 | $box-background-color: #999999; 13 | 14 | // Border radius 15 | ////$radius: 4; 16 | 17 | @import "bulma"; 18 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; -------------------------------------------------------------------------------- /app/helpers/format-navtitle.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function formatNavtitle([dottedpath]) { 4 | let title = 'tables'; 5 | let arr = dottedpath.split('.'); 6 | if (arr.length == 1) { 7 | title = dottedpath; 8 | } else { 9 | // Nested path, assume nav UI is "tables" 10 | title = arr[0]; 11 | } 12 | return `${title}`; 13 | } 14 | 15 | export default helper(formatNavtitle); 16 | -------------------------------------------------------------------------------- /app/helpers/phone-preference.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function phonePreference([patient]) { 4 | let phone = 'Default'; 5 | 6 | patient.telecom.some(function(tele) { 7 | if (tele.system === 'phone') { 8 | phone = tele.value; 9 | return true; 10 | } 11 | return false; 12 | }); 13 | 14 | return `${phone}`; 15 | } 16 | 17 | export default helper(phonePreference); 18 | -------------------------------------------------------------------------------- /app/models/patient.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | address: DS.attr(), 5 | birthDate: DS.attr(), 6 | communication: DS.attr(), 7 | deceased: DS.attr(), 8 | gender: DS.attr(), 9 | identifier: DS.attr(), 10 | maritalStatus: DS.attr(), 11 | multipleBirth: DS.attr(), 12 | name: DS.attr(), 13 | resourceType: DS.attr(), 14 | telecom: DS.attr(), 15 | text: DS.attr() 16 | }); 17 | -------------------------------------------------------------------------------- /app/helpers/address-country.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function addressCountry([patient]) { 4 | let country = 'Default'; 5 | 6 | patient.address.some(function(ad) { 7 | if (typeof ad.line !== 'undefined') { 8 | country = ad.country; 9 | return true; 10 | } 11 | return false; 12 | }); 13 | 14 | return `${country}`; 15 | } 16 | 17 | export default helper(addressCountry); 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /app/helpers/language-preference.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function languagePreference([patient]) { 4 | let lang = 'Default'; 5 | 6 | patient.communication.some(function(comm) { 7 | if (typeof comm.language !== 'undefined') { 8 | lang = comm.language.text; 9 | return true; 10 | } 11 | return false; 12 | }); 13 | 14 | return `${lang}`; 15 | } 16 | 17 | export default helper(languagePreference); 18 | -------------------------------------------------------------------------------- /app/components/table-listing.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | import { pluralize } from 'ember-inflector'; 4 | 5 | export default Component.extend({ 6 | router: service(), 7 | 8 | actions: { 9 | zoom(rtype) { 10 | // The show-btn click action, to zoom the resource-type. 11 | let dest = pluralize(rtype); 12 | this.get('router').transitionTo(dest); 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /app/helpers/official-name.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function officialName([patient]) { 4 | let family = ''; 5 | let given = ''; 6 | 7 | patient.name.some(function(name) { 8 | if (name.use == "official") { 9 | family = name.family; 10 | given = name.given[0]; 11 | return true; 12 | } 13 | return false; 14 | }); 15 | 16 | return `${given} ${family}`; 17 | } 18 | 19 | export default helper(officialName); 20 | -------------------------------------------------------------------------------- /app/helpers/medrecord-number.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function medrecordNumber([patient]) { 4 | let card = ''; 5 | 6 | patient.identifier.some(function(id) { 7 | if (typeof id.type !== 'undefined') { 8 | if (id.type.coding[0].code === 'MR') { 9 | card = id.value; 10 | return true; 11 | } 12 | } 13 | return false; 14 | }); 15 | 16 | return `${card}`; 17 | } 18 | 19 | export default helper(medrecordNumber); 20 | -------------------------------------------------------------------------------- /app/helpers/passport-number.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function passportNumber([patient]) { 4 | let card = ''; 5 | 6 | patient.identifier.some(function(id) { 7 | if (typeof id.type !== 'undefined') { 8 | if (id.type.coding[0].code === 'PPN') { 9 | card = id.value; 10 | return true; 11 | } 12 | } 13 | return false; 14 | }); 15 | 16 | return `${card}`; 17 | } 18 | 19 | export default helper(passportNumber); 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/ 15 | /libpeerconnection.log 16 | /npm-debug.log* 17 | /testem.log 18 | /yarn-error.log 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /package.json.ember-try 24 | 25 | # firebase 26 | /.firebase 27 | -------------------------------------------------------------------------------- /app/helpers/format-ssn.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function formatSsn([patient]) { 4 | let card = ''; 5 | 6 | patient.identifier.some(function(id) { 7 | if (typeof id.type !== 'undefined') { 8 | if (id.type.coding[0].code === 'SB') { 9 | // only right most 4 digits 10 | card = id.value.slice(-4); 11 | return true; 12 | } 13 | } 14 | return false; 15 | }); 16 | 17 | return `***-**-${card}`; 18 | } 19 | 20 | export default helper(formatSsn); 21 | -------------------------------------------------------------------------------- /app/helpers/drivers-license.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function driversLicense([patient]) { 4 | let card = ''; 5 | 6 | patient.identifier.some(function(id) { 7 | if (typeof id.type !== 'undefined') { 8 | ////if (id.system === 'urn:oid:2.16.840.1.113883.4.3.25') { 9 | if (id.type.coding[0].code === 'DL') { 10 | card = id.value; 11 | return true; 12 | } 13 | } 14 | return false; 15 | }); 16 | 17 | return `${card}`; 18 | } 19 | 20 | export default helper(driversLicense); 21 | -------------------------------------------------------------------------------- /app/templates/components/table-listing.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{#each @tables as |history|}} 6 | 7 | 8 | 9 | 14 | 15 | {{/each}} 16 | 17 |
{{history.resourceType}}{{history.count}} 10 | 13 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /app/helpers/address-line1.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function addressLine1([patient]) { 4 | let line1 = 'Default'; 5 | let city = ''; 6 | let state = ''; 7 | let zip = ''; 8 | 9 | patient.address.some(function(ad) { 10 | if (typeof ad.line !== 'undefined') { 11 | line1 = ad.line[0]; 12 | city = ad.city; 13 | if (typeof ad.state !== 'undefined') { 14 | state = ad.state; 15 | } 16 | if (typeof ad.postalCode !== 'undefined') { 17 | zip = ad.postalCode; 18 | } 19 | return true; 20 | } 21 | return false; 22 | }); 23 | 24 | return `${line1}, ${city}, ${state} ${zip}`; 25 | } 26 | 27 | export default helper(addressLine1); 28 | -------------------------------------------------------------------------------- /app/templates/components/donut-chart.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#each @segments as |seg index|}} 3 | {{#if (gt seg.count 0)}} 4 | 12 | {{get this.text index}} 13 | {{/if}} 14 | {{/each}} 15 | 16 | -------------------------------------------------------------------------------- /app/routes/dashboard.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | queryParams: { 5 | offset: { 6 | refreshModel: true 7 | }, 8 | limit: { 9 | refreshModel: true 10 | }, 11 | sort: { 12 | refreshModel: true 13 | } 14 | }, 15 | 16 | model(params) { 17 | return this.store.query('change', { 18 | page: {size: params.limit, number: params.offset}, 19 | sort: params.sort 20 | }).then((results) => { 21 | return { 22 | tables: results, 23 | meta: results.get('links') 24 | }; 25 | }); 26 | }, 27 | setupController(controller, {tables, meta}) { 28 | this._super(controller, tables); 29 | controller.set('meta', meta); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /app/routes/tables.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | queryParams: { 5 | offset: { 6 | refreshModel: true 7 | }, 8 | limit: { 9 | refreshModel: true 10 | }, 11 | sort: { 12 | refreshModel: true 13 | } 14 | }, 15 | 16 | model(params) { 17 | return this.store.query('change', { 18 | page: {size: params.limit, number: params.offset}, 19 | sort: params.sort 20 | }).then((results) => { 21 | return { 22 | tables: results, 23 | meta: results.get('links') 24 | }; 25 | }); 26 | }, 27 | setupController(controller, {tables, meta}) { 28 | this._super(controller, tables); 29 | controller.set('meta', meta); 30 | } 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FHIR admin 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/routes/patients.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | queryParams: { 5 | offset: { 6 | refreshModel: true 7 | }, 8 | limit: { 9 | refreshModel: true 10 | }, 11 | sort: { 12 | refreshModel: true 13 | } 14 | }, 15 | 16 | model(params) { 17 | return this.store.query('patient', { 18 | page: {size: params.limit, number: params.offset}, 19 | sort: params.sort 20 | }).then((results) => { 21 | return { 22 | patients: results, 23 | meta: results.get('links') 24 | }; 25 | }); 26 | }, 27 | setupController(controller, {patients, meta}) { 28 | this._super(controller, patients); 29 | controller.set('meta', meta); 30 | } 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /app/routes/encounters.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | queryParams: { 5 | offset: { 6 | refreshModel: true 7 | }, 8 | limit: { 9 | refreshModel: true 10 | }, 11 | sort: { 12 | refreshModel: true 13 | } 14 | }, 15 | 16 | model(params) { 17 | return this.store.query('encounter', { 18 | page: {size: params.limit, number: params.offset}, 19 | sort: params.sort 20 | }).then((results) => { 21 | return { 22 | encounters: results, 23 | meta: results.get('links') 24 | }; 25 | }); 26 | }, 27 | setupController(controller, {encounters, meta}) { 28 | this._super(controller, encounters); 29 | controller.set('meta', meta); 30 | } 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /app/routes/observations.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | queryParams: { 5 | offset: { 6 | refreshModel: true 7 | }, 8 | limit: { 9 | refreshModel: true 10 | }, 11 | sort: { 12 | refreshModel: true 13 | } 14 | }, 15 | 16 | model(params) { 17 | return this.store.query('observation', { 18 | page: {size: params.limit, number: params.offset}, 19 | sort: params.sort 20 | }).then((results) => { 21 | return { 22 | observations: results, 23 | meta: results.get('links') 24 | }; 25 | }); 26 | }, 27 | setupController(controller, {observations, meta}) { 28 | this._super(controller, observations); 29 | controller.set('meta', meta); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /app/templates/components/patient-listing.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{#each @patients as |patient|}} 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | {{/each}} 18 | 19 |
{{patient.resourceType}}{{patient.id}}{{official-name patient}} 11 | 12 | 14 | Delete 15 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /app/templates/components/encounter-listing.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{#each @encounters as |encounter|}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 18 | 19 | {{/each}} 20 | 21 |
{{encounter.resourceType}}{{encounter.id}}{{encounter.type}}{{encounter.period}}{{encounter.status}} 13 | 14 | 16 | Delete 17 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberApp(defaults, { 7 | sassOptions: { 8 | includePaths: [ 9 | 'node_modules/bulma' 10 | ] 11 | } 12 | }); 13 | 14 | // Use `app.import` to add additional libraries to the generated 15 | // output files. 16 | // 17 | // If you need to use different assets in different 18 | // environments, specify an object as the first parameter. That 19 | // object's keys should be the environment name and the values 20 | // should be the asset to use in that environment. 21 | // 22 | // If the library that you are including contains AMD or ES6 23 | // modules that you would like to import into your application 24 | // please specify an object with the list of modules as keys 25 | // along with the exports of each module as its value. 26 | 27 | return app.toTree(); 28 | }; 29 | -------------------------------------------------------------------------------- /app/templates/components/observation-listing.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{#each @observations as |observation|}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 18 | 19 | {{/each}} 20 | 21 |
{{observation.resourceType}}{{observation.id}}{{observation.effective}}{{moment observation.issued}}{{observation.status}} 13 | 14 | 16 | Delete 17 |
22 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from "@ember/routing/router"; 2 | import config from "./config/environment"; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('about'); 11 | this.route('dashboard', function() { 12 | this.route('index', {path: '/'}); 13 | }); 14 | this.route('tables', function() { 15 | this.route('index', {path: '/'}); 16 | }); 17 | this.route('patients', function(){ 18 | this.route('patient', {path: '/:patient_id'}); 19 | this.route('index', {path: '/'}); 20 | }); 21 | this.route('encounters', function(){ 22 | this.route('encounter', {path: '/:encounter_id'}); 23 | this.route('index', {path: '/'}); 24 | }); 25 | this.route('observations', function(){ 26 | this.route('observation', {path: '/:observation_id'}); 27 | this.route('index', {path: '/'}); 28 | }); 29 | 30 | }); 31 | 32 | export default Router; 33 | -------------------------------------------------------------------------------- /app/templates/components/nav-titlebar.hbs: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 2017, 6 | sourceType: 'module' 7 | }, 8 | plugins: [ 9 | 'ember' 10 | ], 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:ember/recommended' 14 | ], 15 | env: { 16 | browser: true 17 | }, 18 | rules: { 19 | }, 20 | overrides: [ 21 | // node files 22 | { 23 | files: [ 24 | '.ember-cli.js', 25 | '.eslintrc.js', 26 | '.template-lintrc.js', 27 | 'ember-cli-build.js', 28 | 'testem.js', 29 | 'blueprints/*/index.js', 30 | 'config/**/*.js', 31 | 'lib/*/index.js' 32 | ], 33 | excludedFiles: [ 34 | 'app/**', 35 | ], 36 | parserOptions: { 37 | sourceType: 'script', 38 | ecmaVersion: 2015 39 | }, 40 | env: { 41 | browser: false, 42 | node: true 43 | }, 44 | plugins: ['node'], 45 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 46 | // add your custom rules and overrides for node files here 47 | }) 48 | } 49 | ] 50 | }; 51 | -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 31 |
32 | 33 |
34 | {{outlet}} 35 |
36 |
37 | -------------------------------------------------------------------------------- /app/templates/about.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | Campfire Photo by Benjamin DeYoung on Unsplash 4 |
5 |
6 |
7 |
8 |
About FHIR admin
9 |

10 | HL7® FHIR® standard is the international guideline for the inter exchange of health data. Go to HL7 to learn more. 11 | This FHIR admin website is a CRUD UI project created in Ember. 12 | It's one step beyond the FHIR ping tool, to demonstrate 13 | more operations in addition to read / update. 14 |

15 |
16 |
17 | #fhirbase 18 | #fhir 19 | #healthcare 20 |
21 |
22 | Photo by Benjamin DeYoung on Unsplash 23 |
24 |
25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /app/components/placeholder-chart.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { computed } from '@ember/object'; 3 | import { map, sum } from '@ember/object/computed'; 4 | 5 | export default Component.extend({ 6 | 7 | points: computed('segments', function() { 8 | return { 9 | 0: this.bars[0], 10 | 1: this.bars[1], 11 | 2: this.bars[2], 12 | 3: this.bars[3], 13 | 4: this.bars[4], 14 | } 15 | }), 16 | 17 | bars: map('ratios', function(ratio, index) { 18 | let y = 100 - Math.round(ratio * 100); 19 | if (y > 99) { 20 | // below viewbox, show as height "zero" 21 | y = 99; 22 | } 23 | let offset = index * 20; 24 | return `${offset},120 ${offset},${y} ${offset + 10},${y} ${offset + 10},120` 25 | }), 26 | 27 | ratios: map('counts', function(count) { 28 | return count / this.dataTotal 29 | }), 30 | 31 | counts: map('segments', function(seg) { 32 | let val = parseInt(seg.count, 10); 33 | if (isNaN(val)) { return 0; } 34 | return val; 35 | }), 36 | 37 | dataTotal: sum('counts'), 38 | 39 | stroke: computed('segments', function() { 40 | return { 41 | 0: "#96CCFF", 42 | 1: "#FFDFDF", 43 | 2: "#FBF1A9", 44 | 3: "#A463F2", 45 | 4: "#FFA3D7", 46 | 5: "#9EEBCF", 47 | } 48 | }) 49 | }); 50 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'fhir-admin', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | EMBER_NATIVE_DECORATOR_SUPPORT: true, 14 | EMBER_METAL_TRACKED_PROPERTIES: true, 15 | EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP: true, 16 | EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS: true, 17 | EMBER_GLIMMER_FN_HELPER: true, 18 | EMBER_GLIMMER_ON_MODIFIER: true 19 | }, 20 | EXTEND_PROTOTYPES: { 21 | // Prevent Ember Data from overriding Date.parse. 22 | Date: false 23 | } 24 | }, 25 | 26 | APP: { 27 | // Here you can pass flags/options to your application instance 28 | // when it is created 29 | } 30 | }; 31 | 32 | if (environment === 'development') { 33 | // ENV.APP.LOG_RESOLVER = true; 34 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 35 | // ENV.APP.LOG_TRANSITIONS = true; 36 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 37 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 38 | } 39 | 40 | if (environment === 'test') { 41 | // Testem prefers this... 42 | ENV.locationType = 'none'; 43 | 44 | // keep test console output quieter 45 | ENV.APP.LOG_ACTIVE_GENERATION = false; 46 | ENV.APP.LOG_VIEW_LOOKUPS = false; 47 | 48 | ENV.APP.rootElement = '#ember-testing'; 49 | ENV.APP.autoboot = false; 50 | } 51 | 52 | if (environment === 'production') { 53 | 54 | } 55 | 56 | return ENV; 57 | }; 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fhir-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "description": "CRUD admin UI for FHIR health data", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "興怡", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "ember build", 15 | "lint:hbs": "ember-template-lint .", 16 | "lint:js": "eslint .", 17 | "start": "ember serve", 18 | "test": "ember test" 19 | }, 20 | "devDependencies": { 21 | "@ember/optional-features": "^0.7.0", 22 | "@glimmer/component": "^0.14.0-alpha.3", 23 | "babel-eslint": "^8.0.2", 24 | "broccoli-asset-rev": "^3.0.0", 25 | "bulma": "^0.7.4", 26 | "ember-auto-import": "^1.2.20", 27 | "ember-cli": "github:ember-cli/ember-cli#70ee46c5dbd3e8292f80e7b7727dba8a0f8eee54", 28 | "ember-cli-app-version": "^3.2.0", 29 | "ember-cli-babel": "^7.7.0", 30 | "ember-cli-dependency-checker": "^3.0.0", 31 | "ember-cli-eslint": "^5.0.0", 32 | "ember-cli-htmlbars": "^3.0.0", 33 | "ember-cli-htmlbars-inline-precompile": "^2.0.0", 34 | "ember-cli-inject-live-reload": "^2.0.1", 35 | "ember-cli-moment-shim": "^3.7.1", 36 | "ember-cli-sass": "^10.0.0", 37 | "ember-cli-sri": "^2.1.1", 38 | "ember-cli-template-lint": "^1.0.0-beta.1", 39 | "ember-cli-uglify": "^2.1.0", 40 | "ember-data": "github:emberjs/data#1df833396855d956b817540923dd89338463fec2", 41 | "ember-export-application-global": "^2.0.0", 42 | "ember-font-awesome": "^4.0.0-rc.4", 43 | "ember-inflector": "^3.0.0", 44 | "ember-load-initializers": "^2.0.0", 45 | "ember-math-helpers": "^2.11.1", 46 | "ember-maybe-import-regenerator": "^0.1.6", 47 | "ember-moment": "^7.8.1", 48 | "ember-qunit": "^4.1.2", 49 | "ember-resolver": "^5.1.3", 50 | "ember-source": "https://s3.amazonaws.com/builds.emberjs.com/canary/shas/ffb453bee66791ebc962574c75b16dd9bdb21708.tgz", 51 | "ember-truth-helpers": "^2.1.0", 52 | "ember-welcome-page": "^3.2.0", 53 | "eslint-plugin-ember": "^6.0.1", 54 | "eslint-plugin-node": "^8.0.1", 55 | "loader.js": "^4.7.0", 56 | "qunit-dom": "^0.8.0", 57 | "sass": "^1.19.0" 58 | }, 59 | "engines": { 60 | "node": "6.* || 8.* || >= 10.*" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/templates/components/nav-pagination.hbs: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /app/components/nav-pagination.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | import { computed } from '@ember/object'; 4 | import { and, not } from '@ember/object/computed'; 5 | 6 | export default Component.extend({ 7 | router: service(), 8 | 9 | actions: { 10 | pageSelf() { 11 | this.get('router').transitionTo({queryParams: {offset: this.pself, limit: this.plimit, sort: this.psort}}); 12 | }, 13 | pagePrev() { 14 | this.get('router').transitionTo({queryParams: {offset: this.pprev, limit: this.plimit, sort: this.psort}}); 15 | }, 16 | pageNext() { 17 | this.get('router').transitionTo({queryParams: {offset: this.pnext, limit: this.plimit, sort: this.psort}}); 18 | }, 19 | pageFirst() { 20 | this.get('router').transitionTo({queryParams: {offset: this.pfirst, limit: this.plimit, sort: this.psort}}); 21 | }, 22 | pageLast() { 23 | this.get('router').transitionTo({queryParams: {offset: this.plast, limit: this.plimit, sort: this.psort}}); 24 | } 25 | }, 26 | pfirst: computed('links.first', function() { 27 | return extractPairsValue(this.get('links').first, /page\[number\]/); 28 | }), 29 | plast: computed('links.last', function() { 30 | return extractPairsValue(this.get('links').last, /page\[number\]/); 31 | }), 32 | pprev: computed('links.prev', function() { 33 | return extractPairsValue(this.get('links').prev, /page\[number\]/); 34 | }), 35 | pnext: computed('links.next', function() { 36 | return extractPairsValue(this.get('links').next, /page\[number\]/); 37 | }), 38 | pself: computed('links.self', function() { 39 | return extractPairsValue(this.get('links').self, /page\[number\]/); 40 | }), 41 | plimit: computed('links.self', function() { 42 | return extractPairsValue(this.get('links').self, /page\[size\]=/); 43 | }), 44 | psort: computed('links.self', function() { 45 | return extractPairsValue(this.get('links').self, /sort=/); 46 | }), 47 | disablePrev: computed('links', function() { 48 | return this.pself == 1; 49 | }), 50 | disableNext: computed('links', function() { 51 | return this.pself >= this.plast; 52 | }), 53 | disableFirst: computed('links', function() { 54 | return this.pself == 1; 55 | }), 56 | disableLast: computed('links', function() { 57 | return this.pself >= this.plast; 58 | }), 59 | validPrev: computed('links', function() { 60 | return this.pprev > this.pfirst; 61 | }), 62 | validNext: computed('links', function() { 63 | return this.pnext < this.plast; 64 | }), 65 | bothDisableValidPrev: and('validPrev', 'disablePrev'), 66 | enablePrev: not('disablePrev'), 67 | bothDisableValidNext: and('validNext', 'disableNext'), 68 | enableNext: not('disableNext') 69 | }); 70 | 71 | function extractPairsValue(url, re) { 72 | let parser = document.createElement('a'); 73 | parser.href = url; 74 | let query = parser.search.substring(1); 75 | let pairs = query.split('&'); 76 | let found; 77 | pairs.some(function(element){ 78 | if (re.test(element)) { 79 | let kv = element.split('='); 80 | found = kv[1]; 81 | return true; 82 | } 83 | return false; 84 | }); 85 | 86 | return found; 87 | } 88 | -------------------------------------------------------------------------------- /app/components/donut-chart.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { computed } from '@ember/object'; 3 | import { map, sum } from '@ember/object/computed'; 4 | 5 | export default Component.extend({ 6 | cx: 80, 7 | cy: 80, 8 | radius: 60, 9 | angleOffset: -90, 10 | 11 | offset: computed('segments', function() { 12 | return { 13 | 0: calcStrokeDashoffset(this.ratios[0], this.circumference), 14 | 1: calcStrokeDashoffset(this.ratios[1], this.circumference), 15 | 2: calcStrokeDashoffset(this.ratios[2], this.circumference), 16 | 3: calcStrokeDashoffset(this.ratios[3], this.circumference), 17 | 4: calcStrokeDashoffset(this.ratios[4], this.circumference), 18 | } 19 | }), 20 | 21 | transform: computed('segments', function() { 22 | return { 23 | 0: this.degrees[0], 24 | 1: this.degrees[1], 25 | 2: this.degrees[2], 26 | 3: this.degrees[3], 27 | 4: this.degrees[4], 28 | } 29 | }), 30 | 31 | textX: computed('segments', function() { 32 | return { 33 | 0: this.labelX[0], 34 | 1: this.labelX[1], 35 | 2: this.labelX[2], 36 | 3: this.labelX[3], 37 | 4: this.labelX[4], 38 | } 39 | }), 40 | textY: computed('segments', function() { 41 | return { 42 | 0: this.labelY[0], 43 | 1: this.labelY[1], 44 | 2: this.labelY[2], 45 | 3: this.labelY[3], 46 | 4: this.labelY[4], 47 | } 48 | }), 49 | text: computed('segments', function() { 50 | return { 51 | 0: this.labels[0], 52 | 1: this.labels[1], 53 | 2: this.labels[2], 54 | 3: this.labels[3], 55 | 4: this.labels[4], 56 | } 57 | }), 58 | 59 | labelX: map('ratios', function(ratio, index) { 60 | let angle = ratio * 360 / 2 + this.degrees[index]; 61 | let radians = degreesToRadians(angle); 62 | return this.radius * Math.cos(radians) + this.cx; 63 | }), 64 | labelY: map('ratios', function(ratio, index) { 65 | let angle = ratio * 360 / 2 + this.degrees[index]; 66 | let radians = degreesToRadians(angle); 67 | return this.radius * Math.sin(radians) + this.cy; 68 | }), 69 | labels: map('ratios', function(ratio) { 70 | return percentageLabel(ratio) 71 | }), 72 | 73 | degrees: map('ratios', function(ratio) { 74 | let angle = this.angleOffset; 75 | this.angleOffset = ratio * 360 + angle; 76 | return angle; 77 | }), 78 | 79 | ratios: map('counts', function(count) { 80 | return count / this.dataTotal 81 | }), 82 | 83 | counts: map('segments', function(seg) { 84 | let val = parseInt(seg.count, 10); 85 | if (isNaN(val)) { return 0; } 86 | return val; 87 | }), 88 | 89 | dataTotal: sum('counts'), 90 | 91 | circumference: computed('radius', function() { 92 | return 2 * Math.PI * this.radius 93 | }), 94 | 95 | stroke: computed('radius', function() { 96 | return { 97 | 0: "#96CCFF", 98 | 1: "#FFDFDF", 99 | 2: "#FBF1A9", 100 | 3: "#A463F2", 101 | 4: "#FFA3D7", 102 | 5: "#9EEBCF", 103 | } 104 | }) 105 | }); 106 | 107 | function calcStrokeDashoffset(ratio, circumference) { 108 | let strokeDiff = ratio * circumference; 109 | return circumference - strokeDiff; 110 | } 111 | 112 | function degreesToRadians(angle) { 113 | return angle * (Math.PI / 180) 114 | } 115 | 116 | function percentageLabel(ratio) { 117 | return `${Math.round(ratio * 100)}%` 118 | } 119 | -------------------------------------------------------------------------------- /app/templates/components/patient-detail.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |

8 | 9 | 10 | 11 | 12 |

13 |
14 |
15 |

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |

34 | 35 | +44 36 | 37 |

38 |

39 | 40 |

41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |

58 | 59 |

60 |
61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 | 90 |
91 |
92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 | 108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |
116 | 117 |
118 |
119 |
120 |
121 | 122 |
123 |
124 | 125 |
126 |
127 |
128 |
129 | 130 |
131 |

132 | This field is private 133 |

134 |
135 |
136 |
137 | 138 |
139 |
140 | 141 |
142 |
143 |
144 |
145 | 146 |
147 |
148 |
149 |
150 | 151 |
152 |
153 | 154 |
155 |
156 |
157 |
158 | 159 |
160 |
161 |
162 |
163 | 164 |
165 |
166 | 167 |
168 |
169 |
170 |
171 | 172 |
173 |
174 |
175 |
176 | 177 |
178 |
179 |   180 |
181 |
182 |
183 |
184 | 187 |
188 |
189 |
190 |
191 | --------------------------------------------------------------------------------