diff --git a/portal/config/eproms/ScheduledJob.json b/portal/config/eproms/ScheduledJob.json
index d8b630fea..2881a9b8b 100644
--- a/portal/config/eproms/ScheduledJob.json
+++ b/portal/config/eproms/ScheduledJob.json
@@ -276,6 +276,14 @@
"schedule": "0 0 0 0 0",
"task": "raise_background_exception_task"
},
+ {
+ "active": false,
+ "args": null,
+ "name": "Populate Patient List",
+ "resourceType": "ScheduledJob",
+ "schedule": "0 0 0 0 0",
+ "task": "cache_patient_list"
+ },
{
"active": true,
"args": null,
diff --git a/portal/migrations/versions/038a1a5f4218_.py b/portal/migrations/versions/038a1a5f4218_.py
new file mode 100644
index 000000000..2762fe6ee
--- /dev/null
+++ b/portal/migrations/versions/038a1a5f4218_.py
@@ -0,0 +1,76 @@
+"""add patient_list table for paginated /patients view
+
+Revision ID: 038a1a5f4218
+Revises: daee63f50d35
+Create Date: 2024-09-30 16:10:26.216512
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '038a1a5f4218'
+down_revision = 'daee63f50d35'
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('patient_list',
+ sa.Column('userid', sa.Integer(), nullable=False),
+ sa.Column('study_id', sa.Text(), nullable=True),
+ sa.Column('firstname', sa.String(length=64), nullable=True),
+ sa.Column('lastname', sa.String(length=64), nullable=True),
+ sa.Column('birthdate', sa.Date(), nullable=True),
+ sa.Column('email', sa.String(length=120), nullable=True),
+ sa.Column('questionnaire_status', sa.Text(), nullable=True),
+ sa.Column('empro_status', sa.Text(), nullable=True),
+ sa.Column('clinician', sa.Text(), nullable=True),
+ sa.Column('action_state', sa.Text(), nullable=True),
+ sa.Column('visit', sa.Text(), nullable=True),
+ sa.Column('empro_visit', sa.Text(), nullable=True),
+ sa.Column('consentdate', sa.DateTime(), nullable=True),
+ sa.Column('empro_consentdate', sa.DateTime(), nullable=True),
+ sa.Column('org_name', sa.Text(), nullable=True),
+ sa.Column('deleted', sa.Boolean(), nullable=True),
+ sa.Column('test_role', sa.Boolean(), nullable=True),
+ sa.Column('org_id', sa.Integer(), nullable=True),
+ sa.Column('last_updated', sa.DateTime(), nullable=False),
+ sa.ForeignKeyConstraint(['org_id'], ['organizations.id'], ),
+ sa.PrimaryKeyConstraint('userid')
+ )
+ op.create_index(op.f('ix_patient_list_action_state'), 'patient_list', ['action_state'], unique=False)
+ op.create_index(op.f('ix_patient_list_birthdate'), 'patient_list', ['birthdate'], unique=False)
+ op.create_index(op.f('ix_patient_list_clinician'), 'patient_list', ['clinician'], unique=False)
+ op.create_index(op.f('ix_patient_list_consentdate'), 'patient_list', ['consentdate'], unique=False)
+ op.create_index(op.f('ix_patient_list_email'), 'patient_list', ['email'], unique=False)
+ op.create_index(op.f('ix_patient_list_empro_consentdate'), 'patient_list', ['empro_consentdate'], unique=False)
+ op.create_index(op.f('ix_patient_list_empro_status'), 'patient_list', ['empro_status'], unique=False)
+ op.create_index(op.f('ix_patient_list_empro_visit'), 'patient_list', ['empro_visit'], unique=False)
+ op.create_index(op.f('ix_patient_list_firstname'), 'patient_list', ['firstname'], unique=False)
+ op.create_index(op.f('ix_patient_list_lastname'), 'patient_list', ['lastname'], unique=False)
+ op.create_index(op.f('ix_patient_list_org_name'), 'patient_list', ['org_name'], unique=False)
+ op.create_index(op.f('ix_patient_list_questionnaire_status'), 'patient_list', ['questionnaire_status'], unique=False)
+ op.create_index(op.f('ix_patient_list_study_id'), 'patient_list', ['study_id'], unique=False)
+ op.create_index(op.f('ix_patient_list_visit'), 'patient_list', ['visit'], unique=False)
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_index(op.f('ix_patient_list_visit'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_study_id'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_questionnaire_status'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_org_name'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_lastname'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_firstname'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_empro_visit'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_empro_status'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_empro_consentdate'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_email'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_consentdate'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_clinician'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_birthdate'), table_name='patient_list')
+ op.drop_index(op.f('ix_patient_list_action_state'), table_name='patient_list')
+ op.drop_table('patient_list')
+ # ### end Alembic commands ###
diff --git a/portal/migrations/versions/5a300be640fb_.py b/portal/migrations/versions/5a300be640fb_.py
new file mode 100644
index 000000000..8dce15c9e
--- /dev/null
+++ b/portal/migrations/versions/5a300be640fb_.py
@@ -0,0 +1,52 @@
+"""empty message
+
+Revision ID: 5a300be640fb
+Revises: 038a1a5f4218
+Create Date: 2024-10-08 14:34:28.085963
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = '5a300be640fb'
+down_revision = '038a1a5f4218'
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint('adherence_data_patient_id_fkey', 'adherence_data')
+ op.create_foreign_key(
+ 'adherence_data_patient_id_fkey',
+ 'adherence_data',
+ 'users', ['patient_id'], ['id'], ondelete='cascade')
+ op.alter_column('patient_list', 'last_updated',
+ existing_type=postgresql.TIMESTAMP(),
+ nullable=True)
+ op.create_foreign_key(
+ 'patient_list_userid_fkey',
+ 'patient_list',
+ 'users', ['userid'], ['id'], ondelete='cascade')
+ op.drop_constraint('research_data_subject_id_fkey', 'research_data', type_='foreignkey')
+ op.create_foreign_key(
+ 'research_data_subject_id_fkey',
+ 'research_data',
+ 'users', ['subject_id'], ['id'], ondelete='cascade')
+ op.drop_constraint('research_data_questionnaire_response_id_fkey', 'research_data', type_='foreignkey')
+ op.create_foreign_key(
+ 'research_data_questionnaire_response_id_fkey',
+ 'research_data',
+ 'questionnaire_responses', ['questionnaire_response_id'], ['id'], ondelete='cascade')
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint('research_data_subject_id_fkey', 'research_data', type_='foreignkey')
+ op.create_foreign_key('research_data_subject_id_fkey', 'research_data', 'users', ['subject_id'], ['id'])
+ op.drop_constraint('patient_list_userid_fkey', 'patient_list', type_='foreignkey')
+ op.alter_column('patient_list', 'last_updated',
+ existing_type=postgresql.TIMESTAMP(),
+ nullable=False)
+ # ### end Alembic commands ###
diff --git a/portal/models/adherence_data.py b/portal/models/adherence_data.py
index 7927982a6..121dea032 100644
--- a/portal/models/adherence_data.py
+++ b/portal/models/adherence_data.py
@@ -23,7 +23,8 @@ class AdherenceData(db.Model):
"""
__tablename__ = 'adherence_data'
id = db.Column(db.Integer, primary_key=True)
- patient_id = db.Column(db.ForeignKey('users.id'), index=True, nullable=False)
+ patient_id = db.Column(
+ db.ForeignKey('users.id', ondelete='cascade'), index=True, nullable=False)
rs_id_visit = db.Column(
db.Text, index=True, nullable=False,
doc="rs_id:visit_name")
diff --git a/portal/models/patient_list.py b/portal/models/patient_list.py
new file mode 100644
index 000000000..9d693bf97
--- /dev/null
+++ b/portal/models/patient_list.py
@@ -0,0 +1,101 @@
+"""Module for PatientList, used specifically to populate and page patients"""
+from datetime import datetime, timedelta
+from ..database import db
+from .research_study import BASE_RS_ID, EMPRO_RS_ID
+
+
+class PatientList(db.Model):
+ """Maintain columns for all list fields, all indexed for quick sort
+
+ Table used to generate pages of results for patient lists. Acts
+ as a cache, values should be updated on any change (questionnaire,
+ demographics, deletion, etc.)
+
+ All columns in both patients and sub-study lists are defined.
+ """
+ __tablename__ = 'patient_list'
+ userid = db.Column(
+ db.ForeignKey('users.id', ondelete='cascade'), primary_key=True, nullable=False)
+ study_id = db.Column(db.Text, index=True)
+ firstname = db.Column(db.String(64), index=True)
+ lastname = db.Column(db.String(64), index=True)
+ birthdate = db.Column(db.Date, index=True)
+ email = db.Column(db.String(120), index=True)
+ questionnaire_status = db.Column(db.Text, index=True)
+ empro_status = db.Column(db.Text, index=True)
+ clinician = db.Column(db.Text, index=True)
+ action_state = db.Column(db.Text, index=True)
+ visit = db.Column(db.Text, index=True)
+ empro_visit = db.Column(db.Text, index=True)
+ consentdate = db.Column(db.DateTime, index=True)
+ empro_consentdate = db.Column(db.DateTime, index=True)
+ org_name = db.Column(db.Text, index=True)
+ deleted = db.Column(db.Boolean, default=False)
+ test_role = db.Column(db.Boolean)
+ org_id = db.Column(db.ForeignKey('organizations.id')) # used for access control
+ last_updated = db.Column(db.DateTime)
+
+
+def patient_list_update_patient(patient_id, research_study_id=None):
+ """Update given patient
+
+ :param research_study_id: define to optimize time for updating
+ only values from the given research_study_id. by default, all columns
+ are (re)set to current info.
+ """
+ from .qb_timeline import qb_status_visit_name
+ from .role import ROLE
+ from .user import User
+ from .user_consent import consent_withdrawal_dates
+ from ..views.clinician import clinician_name_map
+
+ user = User.query.get(patient_id)
+ if not user.has_role(ROLE.PATIENT.value):
+ return
+
+ patient = PatientList.query.get(patient_id)
+ new_record = False
+ if not patient:
+ new_record = True
+ patient = PatientList(userid=patient_id)
+ db.session.add(patient)
+
+ if research_study_id is None or new_record:
+ patient.study_id = user.external_study_id
+ patient.firstname = user.first_name
+ patient.lastname = user.last_name
+ patient.email = user.email
+ patient.birthdate = user.birthdate
+ patient.deleted = user.deleted_id is not None
+ patient.test_role = True if user.has_role(ROLE.TEST.value) else False
+ patient.org_id = user.organizations[0].id if user.organizations else None
+ patient.org_name = user.organizations[0].name if user.organizations else None
+
+ # necessary to avoid recursive loop via some update paths
+ now = datetime.utcnow()
+ if patient.last_updated and patient.last_updated + timedelta(seconds=10) > now:
+ db.session.commit()
+ return
+
+ patient.last_updated = now
+ if research_study_id == BASE_RS_ID or research_study_id is None:
+ rs_id = BASE_RS_ID
+ qb_status = qb_status_visit_name(
+ patient.userid, research_study_id=rs_id, as_of_date=now)
+ patient.questionnaire_status = str(qb_status['status'])
+ patient.visit = qb_status['visit_name']
+ patient.consentdate, _ = consent_withdrawal_dates(user=user, research_study_id=rs_id)
+
+ if (research_study_id == EMPRO_RS_ID or research_study_id is None) and user.clinicians:
+ rs_id = EMPRO_RS_ID
+ patient.clinician = '; '.join(
+ (clinician_name_map().get(c.id, "not in map") for c in user.clinicians)) or ""
+ qb_status = qb_status_visit_name(
+ patient.userid, research_study_id=rs_id, as_of_date=now)
+ patient.empro_status = str(qb_status['status'])
+ patient.empro_visit = qb_status['visit_name']
+ patient.action_state = qb_status['action_state'].title() \
+ if qb_status['action_state'] else ""
+ patient.empro_consentdate, _ = consent_withdrawal_dates(
+ user=user, research_study_id=rs_id)
+ db.session.commit()
diff --git a/portal/models/reporting.py b/portal/models/reporting.py
index 1adbc7e2a..9db76b46b 100644
--- a/portal/models/reporting.py
+++ b/portal/models/reporting.py
@@ -6,6 +6,7 @@
from flask import current_app
from flask_babel import force_locale
+from flask_login import login_manager
from werkzeug.exceptions import Unauthorized
from ..audit import auditable_event
@@ -53,6 +54,10 @@ def single_patient_adherence_data(patient_id, research_study_id):
if not patient.has_role(ROLE.PATIENT.value):
return
+ # keep patient list data in sync
+ from .patient_list import patient_list_update_patient
+ patient_list_update_patient(patient_id=patient_id, research_study_id=research_study_id)
+
as_of_date = datetime.utcnow()
cache_moderation = CacheModeration(key=ADHERENCE_DATA_KEY.format(
patient_id=patient_id,
diff --git a/portal/models/research_data.py b/portal/models/research_data.py
index b9aab529a..771cb4613 100644
--- a/portal/models/research_data.py
+++ b/portal/models/research_data.py
@@ -23,9 +23,11 @@ class ResearchData(db.Model):
"""
__tablename__ = 'research_data'
id = db.Column(db.Integer, primary_key=True)
- subject_id = db.Column(db.ForeignKey('users.id'), index=True, nullable=False)
+ subject_id = db.Column(
+ db.ForeignKey('users.id', ondelete='cascade'), index=True, nullable=False)
questionnaire_response_id = db.Column(
- db.ForeignKey('questionnaire_responses.id'), index=True, unique=True, nullable=False,
+ db.ForeignKey('questionnaire_responses.id', ondelete='cascade'),
+ index=True, unique=True, nullable=False,
doc="source questionnaire response")
instrument = db.Column(db.Text, index=True, nullable=False)
research_study_id = db.Column(db.Integer, index=True, nullable=False)
diff --git a/portal/static/js/src/admin.js b/portal/static/js/src/admin.js
index f849e3ad7..8d757c186 100644
--- a/portal/static/js/src/admin.js
+++ b/portal/static/js/src/admin.js
@@ -2,1260 +2,1661 @@ import tnthAjax from "./modules/TnthAjax.js";
import tnthDates from "./modules/TnthDate.js";
import Utility from "./modules/Utility.js";
import CurrentUser from "./mixins/CurrentUser.js";
-import {EPROMS_MAIN_STUDY_ID, EPROMS_SUBSTUDY_ID} from "./data/common/consts.js";
+import {
+ EPROMS_MAIN_STUDY_ID,
+ EPROMS_SUBSTUDY_ID,
+} from "./data/common/consts.js";
-(function () { /*global Vue DELAY_LOADING i18next $ */
- var DELAY_LOADING = true; //a workaround for hiding of loading indicator upon completion of loading of portal wrapper - loading indicator needs to continue displaying until patients list has finished loading
- $.ajaxSetup({
- contentType: "application/json; charset=utf-8"
- });
- var AdminObj = window.AdminObj = new Vue({
- el: "#adminTableContainer",
- errorCaptured: function (Error, Component, info) {
- console.error("Error: ", Error, " Component: ", Component, " Message: ", info); /* console global */
- this.setContainerVis();
- return false;
+let requestTimerId = 0;
+(function () {
+ /*global Vue DELAY_LOADING i18next $ */
+ var DELAY_LOADING = true; //a workaround for hiding of loading indicator upon completion of loading of portal wrapper - loading indicator needs to continue displaying until patients list has finished loading
+ $.ajaxSetup({
+ contentType: "application/json; charset=utf-8",
+ });
+ window.AdminObj = new Vue({
+ el: "#adminTableContainer",
+ errorCaptured: function (Error, Component, info) {
+ console.error(
+ "Error: ",
+ Error,
+ " Component: ",
+ Component,
+ " Message: ",
+ info
+ ); /* console global */
+ this.setContainerVis();
+ return false;
+ },
+ errorHandler: function (err, vm) {
+ this.dataError = true;
+ console.warn("Admin Vue instance threw an error: ", vm, this);
+ console.error("Error thrown: ", err);
+ this.setError("Error occurred initializing Admin Vue instance.");
+ this.setContainerVis();
+ },
+ created: function () {
+ this.injectDependencies();
+ },
+ mounted: function () {
+ var self = this;
+ Utility.VueErrorHandling(); /* global VueErrorHandling */
+ this.preConfig(function () {
+ if ($("#adminTable").length > 0) {
+ self.setLoaderContent();
+ self.rowLinkEvent();
+ self.initToggleListEvent();
+ self.initExportReportDataSelector();
+ self.initTableEvents();
+ self.handleDeletedUsersVis();
+ self.setRowItemEvent();
+ self.handleAffiliatedUIVis();
+ if (self.userId) {
+ self.handleCurrentUser();
+ }
+ if (!self.isPatientsList()) {
+ setTimeout(function () {
+ self.setContainerVis();
+ }, 350);
+ }
+ } else {
+ self.handleCurrentUser();
+ }
+ });
+ },
+ mixins: [CurrentUser],
+ data: {
+ dataError: false,
+ configured: false,
+ initIntervalId: 0,
+ accessed: false,
+ sortFilterEnabled: false,
+ showDeletedUsers: false,
+ orgsSelector: {
+ selectAll: false,
+ clearAll: false,
+ close: false,
+ },
+ ROW_ID_PREFIX: "data_row_",
+ ROW_ID: "userid",
+ tableIdentifier: "adminList",
+ popoverEventInitiated: false,
+ dependencies: {},
+ tableConfig: {
+ formatShowingRows: function (pageFrom, pageTo, totalRows) {
+ var rowInfo;
+ setTimeout(function () {
+ rowInfo = i18next
+ .t("Showing {pageFrom} to {pageTo} of {totalRows} users")
+ .replace("{pageFrom}", pageFrom)
+ .replace("{pageTo}", pageTo)
+ .replace("{totalRows}", totalRows);
+ $(".pagination-detail .pagination-info").html(rowInfo);
+ }, 10);
+ return rowInfo;
},
- errorHandler: function (err, vm) {
- this.dataError = true;
- var errorElement = document.getElementById("admin-table-error-message");
- if (errorElement) {
- errorElement.innerHTML = "Error occurred initializing Admin Vue instance.";
- }
- console.warn("Admin Vue instance threw an error: ", vm, this);
- console.error("Error thrown: ", err);
- this.setContainerVis();
+ formatRecordsPerPage: function (pageNumber) {
+ return i18next
+ .t("{pageNumber} records per page")
+ .replace("{pageNumber}", pageNumber);
},
- created: function () {
- this.injectDependencies();
+ formatToggle: function () {
+ return i18next.t("Toggle");
},
- mounted: function () {
- var self = this;
- Utility.VueErrorHandling(); /* global VueErrorHandling */
- this.preConfig(function () {
- if ($("#adminTable").length > 0) {
- self.setLoaderContent();
- self.rowLinkEvent();
- self.initToggleListEvent();
- self.initExportReportDataSelector();
- self.initTableEvents();
- self.handleDeletedUsersVis();
- self.setRowItemEvent();
- self.handleAffiliatedUIVis();
- self.addFilterPlaceHolders();
- if (self.userId) {
- self.handleCurrentUser();
- self.setColumnSelections();
- self.setTableFilters(self.userId); //set user's preference for filter(s)
- }
- setTimeout(function() {
- self.setContainerVis();
- }, 350);
- } else {
- self.handleCurrentUser();
- }
- });
+ formatColumns: function () {
+ return i18next.t("Columns");
},
- mixins: [CurrentUser],
- data: {
- dataError: false,
- configured: false,
- initIntervalId: 0,
- sortFilterEnabled: false,
- showDeletedUsers: false,
- orgsSelector: {
- selectAll: false,
- clearAll: false,
- close: false
- },
- ROW_ID_PREFIX: "data_row_",
- tableIdentifier: "adminList",
- popoverEventInitiated: false,
- dependencies: {},
- tableConfig: {
- formatShowingRows: function (pageFrom, pageTo, totalRows) {
- var rowInfo;
- setTimeout(function () {
- rowInfo = i18next.t("Showing {pageFrom} to {pageTo} of {totalRows} users").replace("{pageFrom}", pageFrom).replace("{pageTo}", pageTo).replace("{totalRows}", totalRows);
- $(".pagination-detail .pagination-info").html(rowInfo);
- }, 10);
- return rowInfo;
- },
- formatRecordsPerPage: function (pageNumber) {
- return i18next.t("{pageNumber} records per page").replace("{pageNumber}", pageNumber);
- },
- formatToggle: function () {
- return i18next.t("Toggle");
- },
- formatColumns: function () {
- return i18next.t("Columns");
- },
- formatAllRows: function () {
- return i18next.t("All rows");
- },
- formatSearch: function () {
- return i18next.t("Search");
- },
- formatNoMatches: function () {
- return i18next.t("No matching records found");
- },
- formatExport: function () {
- return i18next.t("Export data");
- }
- },
- currentTablePreference: null,
- errorCollection: {
- orgs: "",
- demo: ""
- },
- patientReports: {
- data: [],
- message: "",
- loading: false
- },
- exportReportTimeoutID: 0,
- exportReportProgressTime: 0,
- arrExportReportTimeoutID: [],
- exportDataType: ""
+ formatAllRows: function () {
+ return i18next.t("All rows");
},
- methods: {
- injectDependencies: function () {
- var self = this;
- window.portalModules = window.portalModules || {}; /*eslint security/detect-object-injection: off */
- window.portalModules["tnthAjax"] = tnthAjax;
- window.portalModules["tnthDates"] = tnthDates;
- for (var key in window.portalModules) {
- if ({}.hasOwnProperty.call(window.portalModules, key)) {
- self.dependencies[key] = window.portalModules[key];
- }
- }
- },
- getDependency: function (key) {
- if (key && this.dependencies.hasOwnProperty(key)) {
- return this.dependencies[key];
- } else {
- throw Error("Dependency " + key + " not found."); //throw error ? should be visible in console
- }
- },
- setLoaderContent: function() {
- $("#adminTableContainer .fixed-table-loading").html("");
- },
- setContainerVis: function() {
- $("#adminTableContainer").addClass("active");
- this.fadeLoader();
- },
- showMain: function () {
- $("#mainHolder").css({
- "visibility": "visible",
- "-ms-filter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",
- "filter": "alpha(opacity=100)",
- "-moz-opacity": 1,
- "-khtml-opacity": 1,
- "opacity": 1
- });
- },
- handleCurrentUser: function() {
- var self = this;
- this.initCurrentUser(function() {
- self.onCurrentUserInit();
- }, true);
- },
- isSubStudyPatientView: function() {
- return $("#patientList").hasClass("substudy");
- },
- allowSubStudyView: function() {
- return this.userResearchStudyIds.indexOf(EPROMS_SUBSTUDY_ID) !== -1;
- },
- setSubStudyUIElements: function() {
- if (this.allowSubStudyView()) {
- $("#patientList .eproms-substudy").removeClass("tnth-hide").show();
- return;
- }
- $("#patientList .eproms-substudy").hide();
- },
- getExportReportUrl: function() {
- let dataType = this.exportDataType||"json";
- let researchStudyID = this.isSubStudyPatientView() ? EPROMS_SUBSTUDY_ID: EPROMS_MAIN_STUDY_ID;
- return `/api/report/questionnaire_status?research_study_id=${researchStudyID}&format=${dataType}`;
- },
- clearExportReportTimeoutID: function() {
- if (!this.arrExportReportTimeoutID.length) {
- return false;
- }
- let self = this;
- for (var index=0; index < self.arrExportReportTimeoutID.length; index++) {
- clearTimeout(self.arrExportReportTimeoutID[index]);
- }
- },
- onBeforeExportReportData: function() {
- $("#exportReportContainer").removeClass("open").popover("show");
- $("#btnExportReport").attr("disabled", true);
- $(".exportReport__status").addClass("active");
- this.clearExportReportTimeoutID();
- this.exportReportProgressTime = new Date();
- let pastInfo = this.getCacheReportInfo();
- if (pastInfo) {
- $(".exportReport__history").html(`${i18next.t("View last result exported on {date}").replace("{date}", tnthDates.formatDateString(pastInfo.date, "iso"))}`);
- }
- },
- onAfterExportReportData: function(options) {
- options = options || {};
- $("#btnExportReport").attr("disabled", false);
- $(".exportReport__status").removeClass("active");
- if (options.error) {
- this.updateProgressDisplay("", "");
- $(".exportReport__error .message").html(`Request to export report data failed.${options.message?"
"+options.message: ""}`);
- $(".exportReport__retry").removeClass("tnth-hide");
- this.clearExportReportTimeoutID();
- return;
- }
+ formatSearch: function () {
+ return i18next.t("Search");
+ },
+ formatNoMatches: function () {
+ return i18next.t("No matching records found");
+ },
+ formatExport: function () {
+ return i18next.t("Export data");
+ },
+ },
+ currentTablePreference: null,
+ errorCollection: {
+ orgs: "",
+ demo: "",
+ },
+ patientReports: {
+ data: [],
+ message: "",
+ loading: false,
+ },
+ exportReportTimeoutID: 0,
+ exportReportProgressTime: 0,
+ arrExportReportTimeoutID: [],
+ exportDataType: "",
+ filterOptionsList: [],
+ },
+ methods: {
+ setError: function (errorMessage) {
+ if (!errorMessage) return;
+ var errorElement = document.getElementById("admin-table-error-message");
+ if (errorElement) {
+ errorElement.innerHTML = errorMessage;
+ }
+ },
+ injectDependencies: function () {
+ var self = this;
+ window.portalModules =
+ window.portalModules ||
+ {}; /*eslint security/detect-object-injection: off */
+ window.portalModules["tnthAjax"] = tnthAjax;
+ window.portalModules["tnthDates"] = tnthDates;
+ for (var key in window.portalModules) {
+ if ({}.hasOwnProperty.call(window.portalModules, key)) {
+ self.dependencies[key] = window.portalModules[key];
+ }
+ }
+ },
+ getDependency: function (key) {
+ if (key && this.dependencies.hasOwnProperty(key)) {
+ return this.dependencies[key];
+ } else {
+ throw Error("Dependency " + key + " not found."); //throw error ? should be visible in console
+ }
+ },
+ setLoaderContent: function () {
+ $("#adminTableContainer .fixed-table-loading").html(
+ ``
+ );
+ },
+ setContainerVis: function () {
+ $("#adminTableContainer").addClass("active");
+ this.fadeLoader();
+ },
+ showMain: function () {
+ $("#mainHolder").css({
+ visibility: "visible",
+ "-ms-filter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",
+ filter: "alpha(opacity=100)",
+ "-moz-opacity": 1,
+ "-khtml-opacity": 1,
+ opacity: 1,
+ });
+ },
+ getRemotePatientListData: function (params) {
+ if (this.accessed) {
+ var self = this;
+ this.setTablePreference(
+ this.userId,
+ this.tableIdentifier,
+ null,
+ null,
+ null,
+ function () {
+ clearTimeout(requestTimerId);
+ requestTimerId = setTimeout(() => {
+ self.patientDataAjaxRequest(params);
+ }, 200);
+ }
+ );
+ return;
+ }
+ this.patientDataAjaxRequest(params);
+ },
+ patientDataAjaxRequest: function (params) {
+ var includeTestUsers = $("#include_test_role").is(":checked");
+ if (includeTestUsers) {
+ params.data["include_test_role"] = true;
+ }
+ params.data["research_study_id"] =
+ this.tableIdentifier === "patientList" ? 0 : 1;
+ console.log("param data ? ", params.data);
+ var url = "/patients/page";
+ var self = this;
+ $.get(url + "?" + $.param(params.data)).then(function (results) {
+ console.log("row results ", results);
+ if (!self.accessed && results && results.options) {
+ self.filterOptionsList = results.options;
+ }
+ self.accessed = true;
+ params.success(results);
+ });
+ },
+ handleCurrentUser: function () {
+ var self = this;
+ this.initCurrentUser(function () {
+ self.onCurrentUserInit();
+ }, true);
+ },
+ isSubStudyPatientView: function () {
+ return $("#patientList").hasClass("substudy");
+ },
+ allowSubStudyView: function () {
+ return this.userResearchStudyIds.indexOf(EPROMS_SUBSTUDY_ID) !== -1;
+ },
+ setSubStudyUIElements: function () {
+ if (this.allowSubStudyView()) {
+ $("#patientList .eproms-substudy").removeClass("tnth-hide").show();
+ return;
+ }
+ $("#patientList .eproms-substudy").hide();
+ },
+ getExportReportUrl: function () {
+ let dataType = this.exportDataType || "json";
+ let researchStudyID = this.isSubStudyPatientView()
+ ? EPROMS_SUBSTUDY_ID
+ : EPROMS_MAIN_STUDY_ID;
+ return `/api/report/questionnaire_status?research_study_id=${researchStudyID}&format=${dataType}`;
+ },
+ clearExportReportTimeoutID: function () {
+ if (!this.arrExportReportTimeoutID.length) {
+ return false;
+ }
+ let self = this;
+ for (
+ var index = 0;
+ index < self.arrExportReportTimeoutID.length;
+ index++
+ ) {
+ clearTimeout(self.arrExportReportTimeoutID[index]);
+ }
+ },
+ onBeforeExportReportData: function () {
+ $("#exportReportContainer").removeClass("open").popover("show");
+ $("#btnExportReport").attr("disabled", true);
+ $(".exportReport__status").addClass("active");
+ this.clearExportReportTimeoutID();
+ this.exportReportProgressTime = new Date();
+ let pastInfo = this.getCacheReportInfo();
+ if (pastInfo) {
+ $(".exportReport__history").html(
+ `${i18next
+ .t("View last result exported on {date}")
+ .replace(
+ "{date}",
+ tnthDates.formatDateString(pastInfo.date, "iso")
+ )}`
+ );
+ }
+ },
+ onAfterExportReportData: function (options) {
+ options = options || {};
+ $("#btnExportReport").attr("disabled", false);
+ $(".exportReport__status").removeClass("active");
+ if (options.error) {
+ this.updateProgressDisplay("", "");
+ $(".exportReport__error .message").html(
+ `Request to export report data failed.${
+ options.message ? "
" + options.message : ""
+ }`
+ );
+ $(".exportReport__retry").removeClass("tnth-hide");
+ this.clearExportReportTimeoutID();
+ return;
+ }
+ $("#exportReportContainer").popover("hide");
+ $(".exportReport__error .message").html("");
+ $(".exportReport__retry").addClass("tnth-hide");
+ },
+ initToggleListEvent: function () {
+ if (!$("#patientListToggle").length) return;
+ $("#patientListToggle a").on("click", (e) => {
+ e.preventDefault();
+ });
+ $("#patientListToggle .radio, #patientListToggle .label").on(
+ "click",
+ function (e) {
+ e.stopImmediatePropagation();
+ $("#patientListToggle").addClass("loading");
+ setTimeout(
+ function () {
+ window.location = $(this).closest("a").attr("href");
+ }.bind(this),
+ 50
+ );
+ }
+ );
+ },
+ initExportReportDataSelector: function () {
+ let self = this;
+ tnthAjax.getConfiguration(this.userId, false, function (data) {
+ if (
+ !data ||
+ !data.PATIENT_LIST_ADDL_FIELDS ||
+ data.PATIENT_LIST_ADDL_FIELDS.indexOf("status") === -1
+ ) {
+ $("#exportReportContainer").hide();
+ return false;
+ }
+ let html = $("#exportReportPopoverWrapper").html();
+ $("#adminTableContainer .fixed-table-toolbar .columns-right").append(
+ html
+ );
+ $("#exportReportContainer").attr(
+ "data-content",
+ $("#exportReportPopoverContent").html()
+ );
+ $("#exportReportContainer .data-types li").each(function () {
+ $(this).attr("title", self.getExportReportUrl());
+ });
+ $("#exportReportContainer").on("shown.bs.popover", function () {
+ $(".exportReport__retry")
+ .off("click")
+ .on("click", function (e) {
+ e.stopImmediatePropagation();
$("#exportReportContainer").popover("hide");
- $(".exportReport__error .message").html("");
- $(".exportReport__retry").addClass("tnth-hide");
- },
- initToggleListEvent: function() {
- if (!$("#patientListToggle").length) return;
- $("#patientListToggle a").on("click", e => {
- e.preventDefault();
- });
- $("#patientListToggle .radio, #patientListToggle .label").on("click", function(e) {
- e.stopImmediatePropagation();
- $("#patientListToggle").addClass("loading");
- setTimeout(function() {
- window.location = $(this).closest("a").attr("href");
- }.bind(this), 50);
- });
- },
- initExportReportDataSelector: function() {
- let self = this;
- tnthAjax.getConfiguration(this.userId, false, function(data) {
- if (!data || !data.PATIENT_LIST_ADDL_FIELDS || data.PATIENT_LIST_ADDL_FIELDS.indexOf("status") === -1) {
- $("#exportReportContainer").hide();
- return false;
- }
- let html = $("#exportReportPopoverWrapper").html();
- $("#adminTableContainer .fixed-table-toolbar .columns-right").append(html);
- $("#exportReportContainer").attr("data-content", $("#exportReportPopoverContent").html());
- $("#exportReportContainer .data-types li").each(function() {
- $(this).attr("title", self.getExportReportUrl());
- });
- $("#exportReportContainer").on("shown.bs.popover", function () {
- $(".exportReport__retry").off("click").on("click", function(e) {
- e.stopImmediatePropagation();
- $("#exportReportContainer").popover("hide");
- setTimeout(function() {
- $("#btnExportReport").trigger("click");
- }, 50);
- });
- });
- $("#exportReportContainer").on("hide.bs.popover", function () {
- self.clearExportReportTimeoutID();
- });
- $("#exportReportContainer .data-types li").on("click", function(e) {
- e.stopPropagation();
- self.exportDataType = $(this).attr("data-type");
- let reportUrl = self.getExportReportUrl();
- self.updateProgressDisplay("", "");
- $.ajax({
- type: "GET",
- url: reportUrl,
- beforeSend: function() {
- self.onBeforeExportReportData();
- },
- success: function(data, status, request) {
- let statusUrl= request.getResponseHeader("Location");
- self.updateExportProgress(statusUrl, function(data) {
- self.onAfterExportReportData(data);
- });
- },
- error: function(xhr) {
- self.onAfterExportReportData({error: true, message: xhr.responseText});
- }
- });
- });
- $("#adminTableContainer .columns-right .export button").attr("title", i18next.t("Export patient list"));
- });
- },
- updateProgressDisplay: function(status, percentage, showLoader) {
- $(".exportReport__percentage").text(percentage);
- $(".exportReport__status").text(status);
- if (showLoader) {
- $(".exportReport__loader").removeClass("tnth-hide");
- } else {
- $(".exportReport__loader").addClass("tnth-hide")
- }
- },
- setCacheReportInfo: function(resultUrl) {
- if (!resultUrl) return false;
- localStorage.setItem("exportReportInfo_"+this.userId+"_"+this.exportDataType, JSON.stringify({
- date: new Date(),
- url: resultUrl
- }));
- },
- getCacheReportInfo: function() {
- let cachedItem = localStorage.getItem("exportReportInfo_"+this.userId+"_"+this.exportDataType);
- if (!cachedItem) return false;
- return JSON.parse(cachedItem);
- },
- updateExportProgress: function(statusUrl, callback) {
- callback = callback || function() {};
- if (!statusUrl) {
- callback({error: true});
- return;
- }
- let self = this;
- // send GET request to status URL
- let rqId = $.getJSON(statusUrl, function(data) {
- if (!data) {
- callback({error: true});
- return;
- }
- let percent = "0%", exportStatus = data["state"].toUpperCase();
- if (data["current"] && data["total"] && parseInt(data["total"]) > 0) {
- percent = parseInt(data['current'] * 100 / data['total']) + "%";
- }
- //update status and percentage displays
- self.updateProgressDisplay(exportStatus, percent, true);
- let arrIncompleteStatus = ["PENDING", "PROGRESS", "STARTED"];
- if (arrIncompleteStatus.indexOf(exportStatus) === -1) {
- if (exportStatus === "SUCCESS") {
- setTimeout(function() {
- let resultUrl = statusUrl.replace("/status", "");
- self.setCacheReportInfo(resultUrl);
- window.location.assign(resultUrl);
- }.bind(self), 50); //wait a bit before retrieving results
- }
- self.updateProgressDisplay(data["state"], "");
- setTimeout(function() {
- callback(exportStatus === "SUCCESS" ? data : {error: true});
- }, 300);
- }
- else {
- //check how long the status stays in pending
- if (exportStatus === "PENDING") {
- let passedTime = ((new Date()).getTime() - self.exportReportProgressTime.getTime()) / 1000;
- if (passedTime > 300) {
- //more than 5 minutes passed and the task is still in PENDING status
- //never advanced to PROGRESS to start the export process
- //abort
- self.onAfterExportReportData({
- "error": true,
- "message": i18next.t("More than 5 minutes spent in pending status.")
- });
- //log error
- tnthAjax.reportError(self.userId, window.location.pathname, "Request to export report data failed. More than 5 minutes spent in pending status.");
- return;
- }
- }
- // rerun in 2 seconds
- self.exportReportTimeoutID = setTimeout(function() {
- self.updateExportProgress(statusUrl, callback);
- }.bind(self), 2000); //each update invocation should be assigned a unique timeoutid
- (self.arrExportReportTimeoutID).push(self.exportReportTimeoutID);
- }
- }).fail(function(xhr) {
- callback({error: true, message: xhr.responseText});
- });
- },
- onCurrentUserInit: function() {
- if (this.userOrgs.length === 0) {
- $("#createUserLink").attr("disabled", true);
- }
- this.handleDisableFields();
- if (this.hasOrgsSelector()) {
- this.initOrgsFilter();
- this.initOrgsEvent();
- }
- this.setSubStudyUIElements();
- this.initRoleBasedEvent();
- this.fadeLoader();
- setTimeout(function() {
- this.setOrgsFilterWarning();
- }.bind(this), 650);
- },
- setOrgsMenuHeight: function (padding) {
- padding = padding || 85;
- var h = parseInt($("#fillOrgs").height());
- if (h > 0) {
- var adminTable = $("div.admin-table"),
- orgMenu = $("#org-menu");
- var calculatedHeight = h + padding;
- $("#org-menu").height(calculatedHeight);
- if (adminTable.height() < orgMenu.height()) {
- setTimeout(function () {
- adminTable.height(orgMenu.height() + calculatedHeight);
- }, 0);
- }
- }
- },
- clearFilterButtons: function () {
- this.setOrgsSelector({
- selectAll: false,
- clearAll: false,
- close: false
- });
- },
- fadeLoader: function () {
- var self = this;
- self.showMain();
setTimeout(function () {
- $("body").removeClass("vis-on-callback");
- $("#loadingIndicator").fadeOut().css("visibility", "hidden");
- }, 150);
- },
- showLoader: function () {
- $("#loadingIndicator").show().css("visibility", "visible");
- },
- preConfig: function (callback) {
- var self = this,
- tnthAjax = this.getDependency("tnthAjax");
- callback = callback || function () {};
- tnthAjax.getCurrentUser(function (data) {
- if (data) {
- self.userId = data.id;
- self.setIdentifier();
- self.setSortFilterProp();
- self.configTable();
- self.configured = true;
- setTimeout(function () {
- callback();
- }, 50);
- } else {
- alert(i18next.t("User Id is required")); /* global i18next */
- self.configured = true;
- return false;
- }
- }, {
- sync: true
+ $("#btnExportReport").trigger("click");
+ }, 50);
+ });
+ });
+ $("#exportReportContainer").on("hide.bs.popover", function () {
+ self.clearExportReportTimeoutID();
+ });
+ $("#exportReportContainer .data-types li").on("click", function (e) {
+ e.stopPropagation();
+ self.exportDataType = $(this).attr("data-type");
+ let reportUrl = self.getExportReportUrl();
+ self.updateProgressDisplay("", "");
+ $.ajax({
+ type: "GET",
+ url: reportUrl,
+ beforeSend: function () {
+ self.onBeforeExportReportData();
+ },
+ success: function (data, status, request) {
+ let statusUrl = request.getResponseHeader("Location");
+ self.updateExportProgress(statusUrl, function (data) {
+ self.onAfterExportReportData(data);
});
- },
- setIdentifier: function () {
- var adminTableContainer = $("#adminTableContainer");
- if (adminTableContainer.hasClass("patient-view")) {
- this.tableIdentifier = "patientList";
- }
- if (adminTableContainer.hasClass("staff-view")) {
- this.tableIdentifier = "staffList";
- }
- if (adminTableContainer.hasClass("substudy")) {
- this.tableIdentifier = "substudyPatientList";
- }
- },
- setOrgsSelector: function (obj) {
- if (!obj) {
- return false;
- }
- var self = this;
- for (var prop in obj) {
- if (self.orgsSelector.hasOwnProperty(prop)) {
- self.orgsSelector[prop] = obj[prop];
- }
- }
- },
- setSortFilterProp: function () {
- this.sortFilterEnabled = (this.tableIdentifier === "patientList" || this.tableIdentifier === "substudyPatientList");
- },
- configTable: function () {
- var options = {};
- var sortObj = this.getTablePreference(this.userId, this.tableIdentifier);
- sortObj = sortObj || this.getDefaultTablePreference();
- options.sortName = sortObj.sort_field;
- options.sortOrder = sortObj.sort_order;
- options.filterBy = sortObj;
- options.exportOptions = { /* global Utility getExportFileName*/
- fileName: Utility.getExportFileName($("#adminTableContainer").attr("data-export-prefix"))
- };
- $("#adminTable").bootstrapTable(this.getTableConfigOptions(options));
- },
- getTableConfigOptions: function (options) {
- if (!options) {
- return this.tableConfig;
- }
- return $.extend({}, this.tableConfig, options);
- },
- initRoleBasedEvent: function() {
- let self = this;
- if (this.isAdminUser()) { /* turn on test account toggle checkbox if admin user */
- $("#frmTestUsersContainer").removeClass("tnth-hide");
- $("#include_test_role").on("click", function() {
- self.showLoader();
- $("#frmTestUsers").submit();
- });
- }
- },
- initTableEvents: function () {
- var self = this;
- $("#adminTable").on("post-body.bs.table", function() {
- self.setContainerVis();
- });
- $("#adminTable").on("reset-view.bs.table", function () {
- self.addFilterPlaceHolders();
- self.resetRowVisByActivationStatus();
- self.setRowItemEvent();
- });
- $("#adminTable").on("search.bs.table", function () {
- self.resetRowVisByActivationStatus();
- self.setRowItemEvent();
- });
- $(window).bind("scroll mousedown mousewheel keyup", function () {
- if ($("html, body").is(":animated")) {
- $("html, body").stop(true, true);
- }
+ },
+ error: function (xhr) {
+ self.onAfterExportReportData({
+ error: true,
+ message: xhr.responseText,
});
- $("#chkDeletedUsersFilter").on("click", function () {
- self.handleDeletedUsersVis();
+ },
+ });
+ });
+ $("#adminTableContainer .columns-right .export button").attr(
+ "title",
+ i18next.t("Export patient list")
+ );
+ });
+ },
+ updateProgressDisplay: function (status, percentage, showLoader) {
+ $(".exportReport__percentage").text(percentage);
+ $(".exportReport__status").text(status);
+ if (showLoader) {
+ $(".exportReport__loader").removeClass("tnth-hide");
+ } else {
+ $(".exportReport__loader").addClass("tnth-hide");
+ }
+ },
+ setCacheReportInfo: function (resultUrl) {
+ if (!resultUrl) return false;
+ localStorage.setItem(
+ "exportReportInfo_" + this.userId + "_" + this.exportDataType,
+ JSON.stringify({
+ date: new Date(),
+ url: resultUrl,
+ })
+ );
+ },
+ getCacheReportInfo: function () {
+ let cachedItem = localStorage.getItem(
+ "exportReportInfo_" + this.userId + "_" + this.exportDataType
+ );
+ if (!cachedItem) return false;
+ return JSON.parse(cachedItem);
+ },
+ updateExportProgress: function (statusUrl, callback) {
+ callback = callback || function () {};
+ if (!statusUrl) {
+ callback({ error: true });
+ return;
+ }
+ let self = this;
+ // send GET request to status URL
+ let rqId = $.getJSON(statusUrl, function (data) {
+ if (!data) {
+ callback({ error: true });
+ return;
+ }
+ let percent = "0%",
+ exportStatus = data["state"].toUpperCase();
+ if (data["current"] && data["total"] && parseInt(data["total"]) > 0) {
+ percent = parseInt((data["current"] * 100) / data["total"]) + "%";
+ }
+ //update status and percentage displays
+ self.updateProgressDisplay(exportStatus, percent, true);
+ let arrIncompleteStatus = ["PENDING", "PROGRESS", "STARTED"];
+ if (arrIncompleteStatus.indexOf(exportStatus) === -1) {
+ if (exportStatus === "SUCCESS") {
+ setTimeout(
+ function () {
+ let resultUrl = statusUrl.replace("/status", "");
+ self.setCacheReportInfo(resultUrl);
+ window.location.assign(resultUrl);
+ }.bind(self),
+ 50
+ ); //wait a bit before retrieving results
+ }
+ self.updateProgressDisplay(data["state"], "");
+ setTimeout(function () {
+ callback(exportStatus === "SUCCESS" ? data : { error: true });
+ }, 300);
+ } else {
+ //check how long the status stays in pending
+ if (exportStatus === "PENDING") {
+ let passedTime =
+ (new Date().getTime() -
+ self.exportReportProgressTime.getTime()) /
+ 1000;
+ if (passedTime > 300) {
+ //more than 5 minutes passed and the task is still in PENDING status
+ //never advanced to PROGRESS to start the export process
+ //abort
+ self.onAfterExportReportData({
+ error: true,
+ message: i18next.t(
+ "More than 5 minutes spent in pending status."
+ ),
});
- if (this.sortFilterEnabled) {
- $("#adminTable").on("sort.bs.table", function (e, name, order) {
- self.setTablePreference(self.userId, self.tableIdentifier, name, order);
- }).on("column-search.bs.table", function () {
- self.setTablePreference(self.userId);
- }).on("column-switch.bs.table", function () {
- self.setTablePreference(self.userId);
- });
+ //log error
+ tnthAjax.reportError(
+ self.userId,
+ window.location.pathname,
+ "Request to export report data failed. More than 5 minutes spent in pending status."
+ );
+ return;
+ }
+ }
+ // rerun in 2 seconds
+ self.exportReportTimeoutID = setTimeout(
+ function () {
+ self.updateExportProgress(statusUrl, callback);
+ }.bind(self),
+ 2000
+ ); //each update invocation should be assigned a unique timeoutid
+ self.arrExportReportTimeoutID.push(self.exportReportTimeoutID);
+ }
+ }).fail(function (xhr) {
+ callback({ error: true, message: xhr.responseText });
+ });
+ },
+ onCurrentUserInit: function () {
+ if (this.userOrgs.length === 0) {
+ $("#createUserLink").attr("disabled", true);
+ }
+ this.handleDisableFields();
+ if (this.hasOrgsSelector()) {
+ this.initOrgsFilter();
+ this.initOrgsEvent();
+ }
+ this.setSubStudyUIElements();
+ this.initRoleBasedEvent();
+ this.fadeLoader();
+ setTimeout(
+ function () {
+ this.setOrgsFilterWarning();
+ }.bind(this),
+ 650
+ );
+ },
+ setOrgsMenuHeight: function (padding) {
+ padding = padding || 85;
+ var h = parseInt($("#fillOrgs").height());
+ if (h > 0) {
+ var adminTable = $("div.admin-table"),
+ orgMenu = $("#org-menu");
+ var calculatedHeight = h + padding;
+ $("#org-menu").height(calculatedHeight);
+ if (adminTable.height() < orgMenu.height()) {
+ setTimeout(function () {
+ adminTable.height(orgMenu.height() + calculatedHeight);
+ }, 0);
+ }
+ }
+ },
+ clearFilterButtons: function () {
+ this.setOrgsSelector({
+ selectAll: false,
+ clearAll: false,
+ close: false,
+ });
+ },
+ fadeLoader: function () {
+ var self = this;
+ self.showMain();
+ setTimeout(function () {
+ $("body").removeClass("vis-on-callback");
+ $("#loadingIndicator").fadeOut().css("visibility", "hidden");
+ }, 150);
+ },
+ showLoader: function () {
+ $("#loadingIndicator").show().css("visibility", "visible");
+ },
+ preConfig: function (callback) {
+ var self = this,
+ tnthAjax = this.getDependency("tnthAjax");
+ callback = callback || function () {};
+ tnthAjax.getCurrentUser(
+ function (data) {
+ if (data) {
+ self.userId = data.id;
+ self.setIdentifier();
+ self.setSortFilterProp();
+ self.configTable();
+ self.configured = true;
+ setTimeout(function () {
+ callback();
+ }, 50);
+ } else {
+ alert(i18next.t("User Id is required")); /* global i18next */
+ self.configured = true;
+ return false;
+ }
+ },
+ {
+ sync: true,
+ }
+ );
+ },
+ setIdentifier: function () {
+ var adminTableContainer = $("#adminTableContainer");
+ if (adminTableContainer.hasClass("patient-view")) {
+ this.tableIdentifier = "patientList";
+ }
+ if (adminTableContainer.hasClass("staff-view")) {
+ this.tableIdentifier = "staffList";
+ }
+ if (adminTableContainer.hasClass("substudy")) {
+ this.tableIdentifier = "substudyPatientList";
+ }
+ },
+ setOrgsSelector: function (obj) {
+ if (!obj) {
+ return false;
+ }
+ var self = this;
+ for (var prop in obj) {
+ if (self.orgsSelector.hasOwnProperty(prop)) {
+ self.orgsSelector[prop] = obj[prop];
+ }
+ }
+ },
+ setSortFilterProp: function () {
+ this.sortFilterEnabled =
+ this.tableIdentifier === "patientList" ||
+ this.tableIdentifier === "substudyPatientList";
+ },
+ setFilterOptionsList: function () {
+ this.filterOptionsList.forEach((o) => {
+ for (const [key, values] of Object.entries(o)) {
+ values.forEach((value) => {
+ if (
+ $(
+ `#adminTable .bootstrap-table-filter-control-${key} option[value='${value[0]}']`
+ ).length > 0
+ ) {
+ // Option exists
+ return true;
+ }
+ $(`#adminTable .bootstrap-table-filter-control-${key}`).append(
+ ``
+ );
+ });
+ }
+ });
+ },
+ configTable: function () {
+ var options = {};
+ var sortObj = this.getTablePreference(
+ this.userId,
+ this.tableIdentifier
+ );
+ sortObj = sortObj || this.getDefaultTablePreference();
+ options.sortName = sortObj.sort_field;
+ options.sortOrder = sortObj.sort_order;
+ options.filterBy = sortObj;
+ options.exportOptions = {
+ /* global Utility getExportFileName*/
+ fileName: Utility.getExportFileName(
+ $("#adminTableContainer").attr("data-export-prefix")
+ ),
+ };
+ $("#adminTable").bootstrapTable(this.getTableConfigOptions(options));
+ },
+ getTableConfigOptions: function (options) {
+ if (!options) {
+ return this.tableConfig;
+ }
+ return $.extend({}, this.tableConfig, options);
+ },
+ initRoleBasedEvent: function () {
+ let self = this;
+ if (this.isAdminUser()) {
+ /* turn on test account toggle checkbox if admin user */
+ $("#frmTestUsersContainer").removeClass("tnth-hide");
+ $("#include_test_role").on("click", function () {
+ $("#adminTable").bootstrapTable("refresh");
+ });
+ }
+ },
+ handleDeletedAccountRows: function (tableData) {
+ const rows = tableData && tableData.rows ? tableData.rows : [];
+ const self = this;
+ $("#adminTable tbody tr").each(function () {
+ const rowId = $(this).attr("data-uniqueid");
+ const isDeleted = rows.find(
+ (o) => parseInt(o[self.ROW_ID]) === parseInt(rowId) && o.deleted
+ );
+ if (!!isDeleted) {
+ $(this).addClass("deleted-user-row");
+ }
+ });
+ },
+ handleDateFields: function (tableData) {
+ const rows = tableData && tableData.rows ? tableData.rows : [];
+ const self = this;
+ $("#adminTable tbody tr").each(function () {
+ const rowId = $(this).attr("data-uniqueid");
+ const matchedRow = rows.find(
+ (o) => parseInt(o[self.ROW_ID]) === parseInt(rowId)
+ );
+ if (matchedRow) {
+ $(this)
+ .find(".birthdate-field")
+ .text(
+ tnthDates.getDateWithTimeZone(matchedRow.birthdate, "d M y")
+ );
+ $(this)
+ .find(".consentdate-field")
+ .text(
+ tnthDates.getDateWithTimeZone(matchedRow.consentdate, "d M y")
+ );
+ }
+ });
+ },
+ initTableEvents: function () {
+ var self = this;
+ $("#adminTable").on("post-body.bs.table", function () {
+ if (!self.isPatientsList()) {
+ self.setContainerVis();
+ }
+ self.setFilterOptionsList();
+ });
+ $("#adminTable").on("load-error.bs.table", function (status, jqXHR) {
+ self.setError(
+ `Error occurred: status ${status}. See console for detail.`
+ );
+ self.setContainerVis();
+ console.error(jqXHR.responseText);
+ });
+ $("#adminTable").on("load-success.bs.table", function (e, data) {
+ self.setColumnSelections();
+ self.addFilterPlaceHolders();
+ self.setTableFilters(self.userId); //set user's preference for filter(s)
+ self.handleDeletedAccountRows(data);
+ self.handleDateFields(data);
+ self.setContainerVis();
+ });
+ $("#adminTable").on("reset-view.bs.table", function () {
+ self.addFilterPlaceHolders();
+ self.resetRowVisByActivationStatus();
+ self.setRowItemEvent();
+ });
+ $("#adminTable").on("search.bs.table", function () {
+ self.resetRowVisByActivationStatus();
+ self.setRowItemEvent();
+ });
+ $("#adminTable").on(
+ "click-row.bs.table",
+ function (e, row, $element, field) {
+ e.stopPropagation();
+ if (row.deleted) return;
+ window.location =
+ "/patients/patient_profile/" + $element.attr("data-uniqueid");
+ }
+ );
+ $(window).bind("scroll mousedown mousewheel keyup", function () {
+ if ($("html, body").is(":animated")) {
+ $("html, body").stop(true, true);
+ }
+ });
+ $("#chkDeletedUsersFilter").on("click", function () {
+ self.handleDeletedUsersVis();
+ });
+ if (this.sortFilterEnabled) {
+ $("#adminTable").on("column-switch.bs.table", function () {
+ self.setTablePreference(self.userId);
+ });
+ }
+ $("#adminTableToolbar .orgs-filter-warning").popover();
+ $("#adminTable .filterControl select").on("change", function () {
+ if ($(this).find("option:selected").val()) {
+ $(this).addClass("active");
+ return;
+ }
+ $(this).removeClass("active");
+ });
+ $("#adminTable .filterControl input").on("change", function () {
+ if ($(this).val()) {
+ $(this).addClass("active");
+ return;
+ }
+ $(this).removeClass("active");
+ });
+ },
+ allowDeletedUserFilter: function () {
+ return $("#chkDeletedUsersFilter").length;
+ },
+ setShowDeletedUsersFlag: function () {
+ if (!this.allowDeletedUserFilter()) {
+ return;
+ }
+ this.showDeletedUsers = $("#chkDeletedUsersFilter").is(":checked");
+ },
+ handleDeletedUsersVis: function () {
+ if (!this.allowDeletedUserFilter()) {
+ return;
+ }
+ this.setShowDeletedUsersFlag();
+ if (this.showDeletedUsers) {
+ $("#adminTable").bootstrapTable("filterBy", {
+ activationstatus: "deactivated",
+ });
+ } else {
+ $("#adminTable").bootstrapTable("filterBy", {
+ activationstatus: "activated",
+ });
+ }
+ },
+ handleAffiliatedUIVis: function () {
+ $(
+ "#adminTableContainer input[data-field='id']:checkbox, #adminTableContainer input[data-field='deactivate']:checkbox, #adminTableContainer input[data-field='activationstatus']:checkbox"
+ )
+ .closest("label")
+ .hide(); //hide checkbox for hidden id field and deactivate account field from side menu
+ $("#patientReportModal").modal({
+ show: false,
+ });
+ },
+ setRowItemEvent: function () {
+ var self = this;
+ $("#adminTableContainer .btn-report")
+ .off("click")
+ .on("click", function (e) {
+ e.stopPropagation();
+ if ($(this).closest(".deleted-user-row").length) {
+ //prevent viewing of report for deleted users
+ return false;
+ }
+ self.getReportModal($(this).attr("data-patient-id"), {
+ documentDataType: $(this).attr("data-document-type"),
+ });
+ });
+ $("#adminTableContainer [name='chkRole']").each(function () {
+ $(this)
+ .off("click")
+ .on("click", function (e) {
+ e.stopPropagation();
+ var userId = $(this).attr("data-user-id");
+ if (!userId) {
+ return false;
+ }
+ var role = $(this).attr("data-role"),
+ checked = $(this).is(":checked"),
+ tnthAjax = self.getDependency("tnthAjax");
+ $("#loadingIndicator_" + userId).show();
+ $("#" + self.ROW_ID_PREFIX + userId).addClass("loading");
+ tnthAjax.getRoles(userId, function (data) {
+ if (!data || data.error) {
+ $("#loadingIndicator_" + userId).hide();
+ $("#" + self.ROW_ID_PREFIX + userId).removeClass("loading");
+ alert(i18next.t("Error occurred retrieving roles for user"));
+ return false;
}
- $("#adminTableToolbar .orgs-filter-warning").popover();
- $("#adminTable .filterControl select").on("change", function() {
- if ($(this).find("option:selected").val()) {
- $(this).addClass("active");
- return;
- }
- $(this).removeClass("active");
+ var arrRoles = data.roles;
+ arrRoles = $.grep(arrRoles, function (item) {
+ return (
+ String(item.name).toLowerCase() !==
+ String(role).toLowerCase()
+ );
});
- $("#adminTable .filterControl input").on("change", function() {
- if ($(this).val()) {
- $(this).addClass("active");
- return;
- }
- $(this).removeClass("active");
- });
- },
- allowDeletedUserFilter: function() {
- return $("#chkDeletedUsersFilter").length;
- },
- setShowDeletedUsersFlag: function () {
- if (!this.allowDeletedUserFilter()) {
- return;
+ if (checked) {
+ arrRoles = arrRoles.concat([{ name: role }]);
}
- this.showDeletedUsers = $("#chkDeletedUsersFilter").is(":checked");
- },
- handleDeletedUsersVis: function () {
- if (!this.allowDeletedUserFilter()) {
- return;
- }
- this.setShowDeletedUsersFlag();
- if (this.showDeletedUsers) {
- $("#adminTable").bootstrapTable("filterBy", {
- activationstatus: "deactivated"
- });
- } else {
- $("#adminTable").bootstrapTable("filterBy", {
- activationstatus: "activated"
- });
- }
- },
- handleAffiliatedUIVis: function () {
- $("#adminTableContainer input[data-field='id']:checkbox, #adminTableContainer input[data-field='deactivate']:checkbox, #adminTableContainer input[data-field='activationstatus']:checkbox").closest("label").hide(); //hide checkbox for hidden id field and deactivate account field from side menu
- $("#patientReportModal").modal({
- "show": false
- });
- },
- setRowItemEvent: function () {
- var self = this;
- $("#adminTableContainer .btn-report").off("click").on("click", function (e) {
- e.stopPropagation();
- if ($(this).closest(".deleted-user-row").length) { //prevent viewing of report for deleted users
- return false;
+ tnthAjax.putRoles(
+ userId,
+ { roles: arrRoles },
+ "",
+ function (data) {
+ $("#loadingIndicator_" + userId).hide();
+ $("#" + self.ROW_ID_PREFIX + userId).removeClass("loading");
+ if (data.error) {
+ alert(i18next.t("Error occurred updating user roles"));
+ return false;
}
- self.getReportModal($(this).attr("data-patient-id"), {
- documentDataType: $(this).attr("data-document-type")
- });
- });
- $("#adminTableContainer [name='chkRole']").each(function() {
- $(this).off("click").on("click", function(e) {
- e.stopPropagation();
- var userId = $(this).attr("data-user-id");
- if (!userId) {
- return false;
- }
- var role = $(this).attr("data-role"), checked = $(this).is(":checked"), tnthAjax = self.getDependency("tnthAjax");
- $("#loadingIndicator_"+userId).show();
- $("#" + self.ROW_ID_PREFIX + userId).addClass("loading");
- tnthAjax.getRoles(userId, function(data) {
- if (!data || data.error) {
- $("#loadingIndicator_"+userId).hide();
- $("#" + self.ROW_ID_PREFIX + userId).removeClass("loading");
- alert(i18next.t("Error occurred retrieving roles for user"));
- return false;
- }
- var arrRoles = data.roles;
- arrRoles = $.grep(arrRoles, function(item) {
- return String(item.name).toLowerCase() !== String(role).toLowerCase();
- });
- if (checked) {
- arrRoles = arrRoles.concat([{name: role}]);
- }
- tnthAjax.putRoles(userId, {roles:arrRoles}, "", function(data) {
- $("#loadingIndicator_"+userId).hide();
- $("#" + self.ROW_ID_PREFIX + userId).removeClass("loading");
- if (data.error) {
- alert(i18next.t("Error occurred updating user roles"));
- return false;
- }
- });
- });
- });
- });
- $("#adminTableContainer .btn-delete-user").each(function () {
- $(this).popover({
- container: "#adminTable",
- html: true,
- content: ["