Visits',\r\n // },\r\n align: 'center',\r\n verticalAlign: 'top',\r\n },\r\n plotOptions: {\r\n series: {\r\n stacking: 'normal',\r\n point: {\r\n events: {\r\n click: function () {\r\n VisitsDialog.ShowDialog(this.options.visits);\r\n }\r\n }\r\n },\r\n },\r\n bar: {\r\n pointWidth: 25,\r\n pointPadding: 0.1,\r\n //groupPadding: 0.2,\r\n borderWidth: 0,\r\n dataLabels: {\r\n enabled: true,\r\n formatter: function () {\r\n if (this.y == 0) return null;\r\n else {\r\n let label: string = Highcharts.numberFormat(this.y, 0, '.', ',');\r\n if (this.point.shapeArgs.height <= (label.length * 10)) return null;\r\n else return label;\r\n }\r\n },\r\n verticalAlign: 'middle',\r\n y: 2\r\n },\r\n },\r\n },\r\n series: data\r\n });\r\n\r\n return chart;\r\n }\r\n\r\n\r\n SetView(newView: string): void {\r\n\r\n if (!newView.startsWith(\"visit-summary\")) { return; }\r\n else if (newView != \"visit-summary\") { //new within-summary view\r\n if (newView.includes(\"byvisit\")) { this.selectedGrouping = \"visit\"; }\r\n else if (newView.includes(\"bysite\")) { this.selectedGrouping = \"site\"; }\r\n\r\n if (newView.includes(\"bystatus\")) { this.selectedData = \"status\"; }\r\n else if (newView.includes(\"bycompletion\")) { this.selectedData = \"completion\"; } \r\n }\r\n\r\n if (this.selectedGrouping == \"visit\" && this.selectedData == \"status\") { SetActiveTab(\"visit-summary-tabs\", \"content-summary-visit-byvisit-status\"); }\r\n else if (this.selectedGrouping == \"site\" && this.selectedData == \"status\") { SetActiveTab(\"visit-summary-tabs\", \"content-summary-visit-bysite-status\"); }\r\n else if (this.selectedGrouping == \"visit\" && this.selectedData == \"completion\") { SetActiveTab(\"visit-summary-tabs\", \"content-summary-visit-byvisit-completion\"); }\r\n else if (this.selectedGrouping == \"site\" && this.selectedData == \"completion\") { SetActiveTab(\"visit-summary-tabs\", \"content-summary-visit-bysite-completion\"); }\r\n\r\n }\r\n\r\n}\r\n\r\n","// extracted by mini-css-extract-plugin","import { Appendix } from \"../../entities/scripts/appendix\";\r\nimport { Site } from \"../../entities/scripts/site\";\r\nimport { Study } from \"../../entities/scripts/study\";\r\nimport { DoArraysOverlap } from \"../array-functions\";\r\n\r\nexport interface IStudyAppendixSiteFilterParent {\r\n FilterUpdated(studyIds: number[], appendixIds: number[], siteIds: number[]);\r\n}\r\n\r\nexport class StudyAppendixSiteFilterSection {\r\n\r\n parent: IStudyAppendixSiteFilterParent;\r\n\r\n studies: Study[] = [];\r\n appendices: Appendix[] = [];\r\n sites: Site[] = [];\r\n\r\n allStudyIds: number[] = []; activeStudyIds: number[] = []; selectedStudyIds: number[] = [];\r\n allAppendixIds: number[] = []; activeAppendixIds: number[] = []; selectedAppendixIds: number[] = [];\r\n allSiteIds: number[] = []; activeSiteIds: number[] = []; selectedSiteIds: number[] = [];\r\n\r\n studySelectId: string;\r\n appendixSelectId: string;\r\n siteSelectId: string;\r\n\r\n constructor(studySelectId: string, appendixSelectId: string, siteSelectId: string, studies: Study[], appendices: Appendix[], sites: Site[], parent: IStudyAppendixSiteFilterParent) {\r\n\r\n this.parent = parent;\r\n\r\n this.studySelectId = studySelectId;\r\n this.appendixSelectId = appendixSelectId;\r\n this.siteSelectId = siteSelectId;\r\n\r\n this.studies = studies;\r\n this.appendices = appendices;\r\n this.sites = sites;\r\n\r\n if (this.studySelectId != undefined) {\r\n Study.SortByName(this.studies);\r\n this.allStudyIds = this.studies.map((s: Study) => s.Id);\r\n }\r\n if (this.appendixSelectId != undefined) {\r\n Appendix.SortByName(this.appendices);\r\n this.allAppendixIds = this.appendices.map((a: Appendix) => a.Id);\r\n }\r\n if (this.siteSelectId != undefined) this.allSiteIds = this.sites.map((s: Site) => s.Id);\r\n\r\n this.initializeFilters();\r\n this.resetFilters();\r\n }\r\n\r\n initializeFilters(): void {\r\n if (this.studySelectId != undefined) this.setupDropdown(\"study\", this.studySelectId, \"Platforms\");\r\n if (this.appendixSelectId != undefined) this.setupDropdown(\"appendix\", this.appendixSelectId, \"Trials\");\r\n if (this.siteSelectId != undefined) this.setupDropdown(\"site\", this.siteSelectId, \"Sites\");\r\n }\r\n\r\n setupDropdown(type: string, elementId: string, optionName: string): void {\r\n\r\n $(elementId).selectpicker({\r\n dropupAuto: false,\r\n selectedTextFormat: \"count\",\r\n\r\n countSelectedText: (numSelected, numTotal) => {\r\n\r\n let activeCount: number = 0;\r\n if (type == 'study') activeCount = this.activeStudyIds.length;\r\n else if (type == 'appendix') activeCount = this.activeAppendixIds.length;\r\n else if (type == 'site') activeCount = this.activeSiteIds.length;\r\n\r\n if (numSelected == numTotal) {\r\n return \"All \" + optionName + \" (n = \" + numSelected + \")\";\r\n }\r\n else {\r\n return numSelected + \" of \" + activeCount + \" \" + optionName + \" Selected\";\r\n }\r\n }\r\n });\r\n $(elementId).selectpicker(\"refresh\");\r\n\r\n $(elementId).on('changed.bs.select', (e, clickedIndex, isSelected, previousValue) => {\r\n let values: string[] =
($(elementId).val());\r\n this.filterDropdownOptions(type, values.map(i => Number(i)));\r\n $(elementId).selectpicker(\"refresh\");\r\n this.filterUpdated(this.selectedStudyIds, this.selectedAppendixIds, this.selectedSiteIds);\r\n });\r\n }\r\n\r\n setDropdownOptions(type: string, elementId: string, selectedElementIds: number[], activeElementIds: number[], allElements: any[], optionParameterValue: string, optionParameterName: string): void {\r\n\r\n $(elementId).empty();\r\n\r\n allElements?.forEach((element: any) => {\r\n let selected: boolean = false;\r\n let disabled: boolean = false;\r\n\r\n if (selectedElementIds.includes(element[optionParameterValue])) {\r\n selected = true;\r\n }\r\n\r\n if (!activeElementIds.includes(element[optionParameterValue])) {\r\n disabled = true;\r\n selected = false;\r\n }\r\n\r\n if (!disabled) {\r\n let option: any = new Option(element[optionParameterName], element[optionParameterValue], false, selected);\r\n //option.disabled = disabled;\r\n $(elementId).append(option);\r\n }\r\n });\r\n $(elementId).selectpicker(\"refresh\");\r\n }\r\n\r\n resetFilters(): void {\r\n this.selectedStudyIds = this.activeStudyIds = this.allStudyIds;\r\n this.selectedAppendixIds = this.activeAppendixIds = this.allAppendixIds;\r\n this.selectedSiteIds = this.activeSiteIds = this.allSiteIds;\r\n\r\n this.setDropdownOptions(\"study\", this.studySelectId, this.selectedStudyIds, this.allStudyIds, this.studies, \"Id\", \"Name\");\r\n this.filterDropdownOptions(\"study\", this.selectedStudyIds);\r\n\r\n this.filterUpdated(this.activeStudyIds, this.activeAppendixIds, this.activeSiteIds);\r\n }\r\n\r\n filterDropdownOptions(type: string, currentValues: number[]) {\r\n\r\n //console.log(type, currentValues);\r\n\r\n if (type == 'study') {\r\n this.selectedStudyIds = currentValues;\r\n this.activeAppendixIds = this.appendices.map((a: Appendix) => {\r\n if (this.selectedStudyIds.includes(a.StudyId)) return a.Id;\r\n }).filter((id: number) => id != undefined);\r\n this.activeSiteIds = this.sites?.map((s: Site) => {\r\n if (DoArraysOverlap(this.selectedStudyIds, s.StudyIds)) return s.Id;\r\n }).filter((id: number) => id != undefined);\r\n\r\n //cohort selection activates all hubs/sites\r\n this.selectedAppendixIds = this.activeAppendixIds;\r\n this.selectedSiteIds = this.activeSiteIds;\r\n }\r\n else if (type == 'appendix') {\r\n this.selectedAppendixIds = currentValues;\r\n //console.log(this.selectedAppendixIds);\r\n //sites not tied to appendices -- only to study\r\n //this.activeSiteIds = this.sites.map((s: Site) => {\r\n // if (this.selectedHubIds.includes(s.HubId + \"\")) return s.Id;\r\n //}).filter((id: string) => id != undefined);\r\n\r\n //this.selectedSiteIds = this.activeSiteIds;\r\n }\r\n else if (type == 'site') {\r\n //no filtering \"left\" to study or appendix based on selected sites\r\n this.selectedSiteIds = currentValues;\r\n }\r\n\r\n if (type != 'study') this.setDropdownOptions(\"study\", this.studySelectId, this.selectedStudyIds, this.allStudyIds, this.studies, \"Id\", \"Name\");\r\n if (type != 'appendix') this.setDropdownOptions(\"appendix\", this.appendixSelectId, this.activeAppendixIds, this.activeAppendixIds, this.appendices, \"Id\", \"Name\");\r\n if (type != 'site') this.setDropdownOptions(\"site\", this.siteSelectId, this.activeSiteIds, this.activeSiteIds, this.sites, \"Id\", \"Name\");\r\n\r\n }\r\n\r\n filterUpdated(studyIds: number[], appendixIds: number[], siteIds: number[]): void {\r\n\r\n //add \"0\" entry to signify all selected\r\n if (this.selectedStudyIds.length == this.activeStudyIds.length) studyIds = [0].concat(studyIds);\r\n if (this.selectedAppendixIds.length == this.activeAppendixIds.length) appendixIds = [0].concat(appendixIds);\r\n if (this.selectedSiteIds?.length == this.activeSiteIds?.length) siteIds = [0].concat(siteIds);\r\n\r\n this.parent.FilterUpdated(studyIds, appendixIds, siteIds);\r\n }\r\n\r\n refreshFilters(): void {\r\n this.filterUpdated(this.selectedStudyIds, this.selectedAppendixIds, this.selectedSiteIds);\r\n }\r\n}","import { VisitEvent } from \"../scripts/visits.entities\";\r\nimport { VisitLookup } from \"../scripts/visits.lookup\";\r\n\r\n\r\n\r\nexport class VisitSummarySeries {\r\n name: string;\r\n //data: Map; //captures both status and completion timing; ID of visit status or timing completion\r\n type: string;\r\n typeId: number; //visitId, siteNumber, etc.\r\n sortValue: number;\r\n visits: Map;\r\n\r\n constructor(name: string, type: string, typeId: number) {\r\n this.name = name;\r\n //this.data = new Map(); \r\n this.type = type;\r\n this.typeId = typeId;\r\n this.sortValue = (type == \"visit\") ? VisitLookup.GetVisitOrdering(typeId) : 0;\r\n this.visits = new Map();\r\n }\r\n\r\n addVisitEvent(event: VisitEvent): void {\r\n\r\n //Visit status\r\n let count: number;\r\n let visitArray: VisitEvent[];\r\n //if (this.data.has(event.VisitStatusId)) { count = this.data.get(event.VisitStatusId); }\r\n //else { count = 0; }\r\n //this.data.set(event.VisitStatusId, ++count);\r\n if (this.visits.has(event.VisitStatusId)) { visitArray = this.visits.get(event.VisitStatusId); }\r\n else { visitArray = []; }\r\n visitArray.push(event);\r\n this.visits.set(event.VisitStatusId, visitArray);\r\n\r\n\r\n if (this.type == \"site\") {\r\n this.sortValue += 1;\r\n }\r\n\r\n if (event.VisitStatusId == 1) { //complete\r\n //if (this.data.has(event.DaysFromWindowId)) { count = this.data.get(event.DaysFromWindowId); }\r\n //else { count = 0; }\r\n //this.data.set(event.DaysFromWindowId, ++count); \r\n if (this.visits.has(event.DaysFromWindowId)) { visitArray = this.visits.get(event.DaysFromWindowId); }\r\n else { visitArray = []; }\r\n visitArray.push(event);\r\n this.visits.set(event.DaysFromWindowId, visitArray); \r\n } \r\n }\r\n}","import { FormatNumberWithCommas } from \"../../../../../shared/scripts/number-functions\";\r\nimport { TableColumnGroup, TableColumnVisibilityController } from \"../../../../../shared/scripts/table-column-visibility\";\r\nimport { DestroyTable, RenderDateMDY, RenderDateYMD, RenderDays } from \"../../../../../shared/scripts/table-functions\";\r\nimport { SiteMilestone } from \"../../../sites/site-activation/scripts/site-activation.entities\";\r\nimport { Contract, ContractHistory } from \"../../scripts/contracts.entities\";\r\n\r\nexport class ContractDetailsTab {\r\n\r\n tableId: string = \"contracts-current-table\";\r\n tableWrapperId: string = this.tableId + \"_wrapper\";\r\n dataTable: any;\r\n tableColumnController: TableColumnVisibilityController;\r\n\r\n modalHistoryData: ContractHistory[];\r\n modalHistoryTableId: string = \"contract-history-table\";\r\n modalHistoryTableWrapper: string = this.modalHistoryTableId + \"_wrapper\";\r\n\r\n modalMilestoneData: SiteMilestone[];\r\n modalMilestoneTableId: string = \"contract-milestone-table\";\r\n modalMilestoneTableWrapper: string = this.modalMilestoneTableId + \"_wrapper\";\r\n\r\n constructor(contracts: Contract[]) {\r\n\r\n this.initializeModals();\r\n\r\n let columns: any[] = [\r\n { data: \"ContractType\", title: \"Contract Type\", className: \"category text-left font-size12 nowrap\" },\r\n { data: { _: \"ContractNameDisplay\", sort: \"ContractName\" }, title: \"Contract Name\", className: \"text-left font-size12\" },\r\n { data: \"ContractNumber\", title: \"Contract Number\", className: \"contract-number text-left font-size12 nowrap\" },\r\n { data: \"PrimeContract\", title: \"Prime Contract\", className: \"prime-contract text-left font-size12 nowrap\" },\r\n { data: \"StatusGroup\", title: \"Status Group\", className: \"status-group text-left font-size12 nowrap\" },\r\n { data: { _: \"StatusDisplay\", sort: \"Status\" }, title: \"Contract Status\", className: \"status link text-left font-size12 nowrap\" },\r\n { data: \"DateContractAdded\", title: \"Contract Added\", className: \"added-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DateLastStatusStart\", title: \"Last Status Change\", className: \"last-status-change-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DateContractStart\", title: \"Contract Started\", className: \"start-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DateContractEnd\", title: \"Contract Ended\", className: \"end-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DateExecuted\", title: \"Date Executed\", className: \"executed-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DaysToExecution\", title: \"Time to Execution\", className: \"days-to-execution text-center font-size12 nowrap\", render: RenderDays },\r\n { data: { _: \"SiteNumberDisplay\", sort: \"SiteNumber\" }, title: \"Site Id\", className: \"site-id text-center font-size12 nowrap\" },\r\n { data: { _: \"MilestoneDisplay\", sort: \"MilestoneDisplay\" }, title: \"Latest Site Milestone\", className: \"milestone link text-left font-size12 nowrap\" },\r\n ];\r\n\r\n this.dataTable = $('#' + this.tableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: true,\r\n pagingType: 'simple',\r\n pageLength: 50,\r\n search: true,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 2\r\n },\r\n columns: columns,\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n }\r\n ],\r\n order: [[0, 'asc'], [1, 'asc'], [6, 'asc']],\r\n data: contracts\r\n });\r\n\r\n $(\"#\" + this.tableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").empty().html('');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .spacer\").addClass('col-12 col-md-3');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").addClass('col-12 col-md-3 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('
');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.tableId, (this.tableId + '-column-select'), [\r\n new TableColumnGroup(\"Contract Number\", false, [], \"contract-number\"),\r\n new TableColumnGroup(\"Prime Contract\", true, [], \"prime-contract\"),\r\n new TableColumnGroup(\"Status Group\", false, [], \"status-group\"),\r\n new TableColumnGroup(\"Status\", true, [], \"status\"),\r\n new TableColumnGroup(\"Date Added\", true, [], \"added-date\"),\r\n new TableColumnGroup(\"Date Last Status Changed\", true, [], \"last-status-change-date\"),\r\n new TableColumnGroup(\"Date Contract Started\", false, [], \"start-date\"),\r\n new TableColumnGroup(\"Date Contract Ended\", false, [], \"end-date\"),\r\n new TableColumnGroup(\"Date Executed\", false, [], \"executed-date\"),\r\n new TableColumnGroup(\"Time to Execution\", false, [], \"days-to-execution\"),\r\n new TableColumnGroup(\"Site Id\", false, [], \"site-id\"), \r\n new TableColumnGroup(\"Site Milestones\", true, [], \"milestones\"), \r\n ]);\r\n\r\n $('#' + this.tableId).on('search.dt', (e, settings) => {\r\n this.setCountFoundLabel(this.dataTable.rows({ search: 'applied' }).count());\r\n });\r\n this.setCountFoundLabel(contracts.length);\r\n }\r\n\r\n setCountFoundLabel(count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + this.tableId + '-found-count').text(\"No Contracts Found\"); break;\r\n case 1: $('#' + this.tableId + '-found-count').text(\"1 Contract Found\"); break;\r\n default: $('#' + this.tableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Contracts Found\");\r\n }\r\n }\r\n\r\n refresh(): void {\r\n this.dataTable.columns.adjust();\r\n this.tableColumnController.refresh();\r\n }\r\n\r\n updateContent(matchingContracts: Contract[]): void {\r\n\r\n //console.log(matchingContracts);\r\n\r\n this.dataTable.clear();\r\n this.dataTable.rows.add(matchingContracts).draw();\r\n this.setCountFoundLabel(matchingContracts.length);\r\n }\r\n\r\n initializeModals(): void {\r\n $('#contract-history-modal').on('shown.bs.modal', () => {\r\n this.initializeHistoryModalTable();\r\n $('#' + this.modalHistoryTableId).DataTable().columns.adjust().draw();\r\n });\r\n\r\n $('#contract-history-modal').on('hidden.bs.modal', () => { DestroyTable('#' + this.modalHistoryTableId); });\r\n\r\n $('#contract-milestone-modal').on('shown.bs.modal', () => {\r\n this.initializeMilestoneModalTable();\r\n $('#' + this.modalMilestoneTableId).DataTable().columns.adjust().draw();\r\n });\r\n\r\n $('#contract-milestone-modal').on('hidden.bs.modal', () => { DestroyTable('#' + this.modalMilestoneTableId); });\r\n }\r\n\r\n showModal(contract: Contract, view: string): void {\r\n let modalId: string = \"\";\r\n\r\n if (view == \"history\") {\r\n this.modalHistoryData = contract.History;\r\n modalId = \"contract-history-modal\";\r\n }\r\n else if (view == \"milestone\") {\r\n this.modalMilestoneData = contract.Milestones;\r\n modalId = \"contract-milestone-modal\";\r\n }\r\n\r\n var modal = $('#' + modalId);\r\n modal.find('#modal-title').html(contract.ContractName);\r\n modal.modal('show');\r\n }\r\n\r\n initializeHistoryModalTable(): void {\r\n $('#' + this.modalHistoryTableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\">>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n search: false,\r\n scrollX: true,\r\n scrollY: '60vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 2\r\n },\r\n columns: [\r\n { data: \"ContractType\", title: \"Contract Type\", className: \"category text-left font-size12 nowrap\" },\r\n { data: { _: \"ContractNameDisplay\", sort: \"ContractName\" }, title: \"Contract Name\", className: \"text-left font-size12\" },\r\n { data: \"ContractNumber\", title: \"Contract Number\", className: \"contract-number text-left font-size12 nowrap\" },\r\n { data: \"PrimeContract\", title: \"Prime Contract\", className: \"prime-contract text-left font-size12 nowrap\" },\r\n { data: \"StatusGroup\", title: \"Status Group\", className: \"status-group text-left font-size12 nowrap\" },\r\n { data: \"Status\", title: \"Status\", className: \"status text-left font-size12 nowrap\" },\r\n { data: \"DateStatusStart\", title: \"Status Start Date\", className: \"start-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"DateStatusEnd\", title: \"Status End Date\", className: \"end-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: \"Duration\", title: \"Duration\", className: \"duration text-center font-size12 nowrap\", render: RenderDays },\r\n { data: \"SortDate\", title: \"SortOrder\", visible: false },\r\n ],\r\n order: [[9, 'asc']],\r\n data: this.modalHistoryData\r\n });\r\n\r\n $(\"#\" + this.modalHistoryTableWrapper + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.modalHistoryTableWrapper + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.modalHistoryTableWrapper + \" .column-select\").empty().html('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.modalHistoryTableId, (this.modalHistoryTableId + '-column-select'), [\r\n new TableColumnGroup(\"Contract Number\", false, [], \"contract-number\"),\r\n new TableColumnGroup(\"Prime Contract\", false, [], \"prime-contract\"),\r\n new TableColumnGroup(\"Status Group\", false, [], \"status-group\"),\r\n new TableColumnGroup(\"Status\", true, [], \"status\"),\r\n new TableColumnGroup(\"Start Date\", true, [], \"start-date\"),\r\n new TableColumnGroup(\"End Date\", true, [], \"end-date\"),\r\n new TableColumnGroup(\"Duration\", true, [], \"duration\"),\r\n ]);\r\n }\r\n\r\n initializeMilestoneModalTable(): void {\r\n $('#' + this.modalMilestoneTableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\">>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n search: false,\r\n scrollX: true,\r\n scrollY: '60vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 1\r\n },\r\n columns: [\r\n { data: { _: \"SiteNameDisplay\", sort: \"SiteName\" }, title: \"Site Name\", className: \"text-left font-size12\" },\r\n { data: { _: \"SiteNumberDisplay\", sort: \"SiteNumber\" }, title: \"Site Id\", className: \"site-id text-center font-size12 nowrap\" },\r\n { data: \"PrimeContract\", title: \"Prime Contract\", className: \"prime-contract text-center font-size12 nowrap\" },\r\n { data: \"Milestone\", title: \"Milestone\", className: \"milestone text-left font-size12 nowrap\" },\r\n { data: \"DateCompletion\", title: \"Completion Date\", className: \"completion-date text-center font-size12 nowrap\", render: RenderDateMDY },\r\n { data: { _: \"CommentDisplay\", sort: \"Comment\" }, title: \"Comments\", className: \"comments text-left font-size12\" },\r\n { data: \"OrderBy\", title: \"OrderBy\", visible: false },\r\n ],\r\n order: [[1, 'asc'], [6, 'asc']],\r\n data: this.modalMilestoneData\r\n });\r\n\r\n $(\"#\" + this.modalMilestoneTableWrapper + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.modalMilestoneTableWrapper + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.modalMilestoneTableWrapper + \" .column-select\").empty().html('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.modalMilestoneTableId, (this.modalMilestoneTableId + '-column-select'), [\r\n new TableColumnGroup(\"Site Id\", true, [], \"site-id\"),\r\n new TableColumnGroup(\"Prime Contract\", true, [], \"prime-contract\"),\r\n new TableColumnGroup(\"Milestone\", true, [], \"milestone\"),\r\n new TableColumnGroup(\"Completion Date\", true, [], \"completion-date\"),\r\n new TableColumnGroup(\"Comments\", true, [], \"comments\"),\r\n ]);\r\n }\r\n\r\n clearSearchBar(): void {\r\n this.dataTable.search('').draw();\r\n }\r\n}","// extracted by mini-css-extract-plugin","import { FormatNumberWithCommas } from \"../../../../shared/scripts/number-functions\";\r\nimport { AggregatedParticipants } from \"./enrollment.entities\";\r\n\r\n\r\n\r\nexport class ProgressBarSection {\r\n\r\n constructor() { }\r\n\r\n updateProgressBarData(aggregatedParticipants: AggregatedParticipants): void {\r\n\r\n this.updateRecoverCohortData(aggregatedParticipants.RecoverCohort, aggregatedParticipants.Count);\r\n this.updateRaceData(aggregatedParticipants.Race, aggregatedParticipants.Count);\r\n this.updateHispanicData(aggregatedParticipants.Hispanic, aggregatedParticipants.Count);\r\n this.updateNonWhiteHispanicData(aggregatedParticipants.NonWhiteHispanic, aggregatedParticipants.Count);\r\n this.updateSexData(aggregatedParticipants.Sex, aggregatedParticipants.Count);\r\n this.updateAgeData(aggregatedParticipants.Age, aggregatedParticipants.Count);\r\n //this.updateSymptomClusterExIntData(aggregatedParticipants.ClusterExInt, aggregatedParticipants.Count);\r\n //this.updateSymptomClusterCogDysData(aggregatedParticipants.ClusterCogn, aggregatedParticipants.Count);\r\n //this.updateSymptomClusterAutonomicData(aggregatedParticipants.ClusterAuto, aggregatedParticipants.Count);\r\n }\r\n\r\n private setProgressBarValue(label: string, valueElementId: string, labelElementId: string, percent: number, count: number): void {\r\n //both title attributes need to be updated for tooltip to refresh\r\n let tooltipText: string = label + \" (\" + Math.round(percent) + \"%; n=\" + FormatNumberWithCommas(String(count)) + \")\";\r\n $(valueElementId).css('width', percent + '%').attr('aria-valuenow', percent).attr('data-title', tooltipText).attr('data-original-title', tooltipText);\r\n $(labelElementId).css('width', percent + '%').attr('aria-valuenow', percent);\r\n $(valueElementId).tooltip();\r\n\r\n if ((label == \"Unknown\" && percent < 12) || (label != \"Unknown\" && percent < 10)) {\r\n //$(labelElementId).addClass('hide');\r\n $(labelElementId).text(\"\");\r\n $(valueElementId).text(\"\");\r\n\r\n }\r\n else {\r\n $(labelElementId).text(label);\r\n //$(labelElementId).removeClass('hide');\r\n $(valueElementId).text(Math.round(percent) + \"%\");\r\n }\r\n }\r\n\r\n private updateRecoverCohortData(data: number[], totalCount: number): void {\r\n\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-recovercohort'); $('#progressbar-message-recovercohort').show(); return; }\r\n else { $('#progressbar-message-recovercohort').hide(); this.showProgressBar('progressbar-recovercohort'); }\r\n\r\n this.setProgressBarValue('Yes', '#value-recovercohort-yes', '#label-recovercohort-yes', ((data[1] / totalCount) * 100), data[1]);\r\n this.setProgressBarValue('No', '#value-recovercohort-no', '#label-recovercohort-no', ((data[0] / totalCount) * 100), data[0]);\r\n this.setProgressBarValue('Unknown', '#value-recovercohort-unknown', '#label-recovercohort-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n }\r\n\r\n private updateRaceData(data: number[], totalCount: number): void {\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-race'); $('#progressbar-message-race').show(); return; }\r\n else { $('#progressbar-message-race').hide(); this.showProgressBar('progressbar-race'); }\r\n\r\n this.setProgressBarValue('AIAN', '#value-race-aian', '#label-race-aian', (data[1] / totalCount) * 100, data[1]);\r\n this.setProgressBarValue('Asian', '#value-race-asian', '#label-race-asian', (data[2] / totalCount) * 100, data[2]);\r\n this.setProgressBarValue('Black', '#value-race-black', '#label-race-black', (data[3] / totalCount) * 100, data[3]);\r\n this.setProgressBarValue('MENA', '#value-race-mena', '#label-race-mena', (data[4] / totalCount) * 100, data[4]);\r\n this.setProgressBarValue('NHPI', '#value-race-nhpi', '#label-race-nhpi', (data[5] / totalCount) * 100, data[5]);\r\n this.setProgressBarValue('White', '#value-race-white', '#label-race-white', (data[6] / totalCount) * 100, data[6]);\r\n this.setProgressBarValue('Multiple', '#value-race-multiple', '#label-race-multiple', (data[0] / totalCount) * 100, data[0]);\r\n this.setProgressBarValue('Unknown', '#value-race-unknown', '#label-race-unknown', (data[7] / totalCount) * 100, data[7]);\r\n }\r\n\r\n private updateHispanicData(data: number[], totalCount: number): void {\r\n\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-hispanic'); $('#progressbar-message-hispanic').show(); return; }\r\n else { $('#progressbar-message-hispanic').hide(); this.showProgressBar('progressbar-hispanic'); }\r\n\r\n this.setProgressBarValue('Hispanic', '#value-hispanic-yes', '#label-hispanic-yes', ((data[1] / totalCount) * 100), data[1]);\r\n this.setProgressBarValue('Not Hispanic', '#value-hispanic-no', '#label-hispanic-no', ((data[0] / totalCount) * 100), data[0]);\r\n this.setProgressBarValue('Unknown', '#value-hispanic-unknown', '#label-hispanic-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n }\r\n\r\n private updateNonWhiteHispanicData(data: number[], totalCount: number): void {\r\n\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-nonwhitehispanic'); $('#progressbar-message-nonwhitehispanic').show(); return; }\r\n else { $('#progressbar-message-nonwhitehispanic').hide(); this.showProgressBar('progressbar-nonwhitehispanic'); }\r\n\r\n this.setProgressBarValue('Yes', '#value-nonwhitehispanic-yes', '#label-nonwhitehispanic-yes', ((data[1] / totalCount) * 100), data[1]);\r\n this.setProgressBarValue('No', '#value-nonwhitehispanic-no', '#label-nonwhitehispanic-no', ((data[0] / totalCount) * 100), data[0]);\r\n this.setProgressBarValue('Unknown', '#value-nonwhitehispanic-unknown', '#label-nonwhitehispanic-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n }\r\n\r\n private updateSexData(data: number[], totalCount: number): void {\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-sex'); $('#progressbar-message-sex').show(); return; }\r\n else { this.showProgressBar('progressbar-sex'); $('#progressbar-message-sex').hide(); }\r\n\r\n this.setProgressBarValue('Female', '#value-sex-female', '#label-sex-female', ((data[0] / totalCount) * 100), data[0]);\r\n this.setProgressBarValue('Male', '#value-sex-male', '#label-sex-male', ((data[1] / totalCount) * 100), data[1]);\r\n this.setProgressBarValue('Intersex', '#value-sex-intersex', '#label-sex-intersex', ((data[2] / totalCount) * 100), data[2]);\r\n this.setProgressBarValue('Prefer Not To Answer', '#value-sex-notanswered', '#label-sex-notanswered', ((data[3] / totalCount) * 100), data[3]);\r\n this.setProgressBarValue('Unknown', '#value-sex-unknown', '#label-sex-unknown', ((data[4] / totalCount) * 100), data[4]);\r\n }\r\n\r\n private updateAgeData(data: number[], totalCount: number): void {\r\n if (totalCount == 0) { this.hideProgressBar('progressbar-age'); $('#progressbar-message-age').show(); return; }\r\n else { $('#progressbar-message-age').hide(); this.showProgressBar('progressbar-age'); }\r\n\r\n this.setProgressBarValue('18-45', '#value-age-18_45', '#label-age-18_45', (data[0] / totalCount) * 100, data[0]);\r\n this.setProgressBarValue('46-65', '#value-age-46_65', '#label-age-46_65', (data[1] / totalCount) * 100, data[1]);\r\n //this.setProgressBarValue('51-70', '#value-age-51_70', '#label-age-51_70', (data[2] / totalCount) * 100, data[2]);\r\n this.setProgressBarValue('>65', '#value-age-more65', '#label-age-more65', (data[2] / totalCount) * 100, data[2]);\r\n this.setProgressBarValue('Unknown', '#value-age-unknown', '#label-age-unknown', (data[3] / totalCount) * 100, data[3]);\r\n }\r\n\r\n //private updateSymptomClusterExIntData(data: number[], totalCount: number): void {\r\n\r\n // if (totalCount == 0) { this.hideProgressBar('progressbar-exint'); $('#progressbar-message-exint').show(); return; }\r\n // else { $('#progressbar-message-exint').hide(); this.showProgressBar('progressbar-exint'); }\r\n\r\n // this.setProgressBarValue('Yes', '#value-exint-yes', '#label-exint-yes', ((data[1] / totalCount) * 100), data[1]);\r\n // this.setProgressBarValue('No', '#value-exint-no', '#label-exint-no', ((data[0] / totalCount) * 100), data[0]);\r\n // this.setProgressBarValue('Unknown', '#value-exint-unknown', '#label-exint-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n //}\r\n\r\n //private updateSymptomClusterCogDysData(data: number[], totalCount: number): void {\r\n\r\n // if (totalCount == 0) { this.hideProgressBar('progressbar-cogdys'); $('#progressbar-message-cogdys').show(); return; }\r\n // else { $('#progressbar-message-cogdys').hide(); this.showProgressBar('progressbar-cogdys'); }\r\n\r\n // this.setProgressBarValue('Yes', '#value-cogdys-yes', '#label-cogdys-yes', ((data[1] / totalCount) * 100), data[1]);\r\n // this.setProgressBarValue('No', '#value-cogdys-no', '#label-cogdys-no', ((data[0] / totalCount) * 100), data[0]);\r\n // this.setProgressBarValue('Unknown', '#value-cogdys-unknown', '#label-cogdys-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n //}\r\n\r\n //private updateSymptomClusterAutonomicData(data: number[], totalCount: number): void {\r\n\r\n // if (totalCount == 0) { this.hideProgressBar('progressbar-autonomic'); $('#progressbar-message-autonomic').show(); return; }\r\n // else { $('#progressbar-message-autonomic').hide(); this.showProgressBar('progressbar-autonomic'); }\r\n\r\n // this.setProgressBarValue('Yes', '#value-autonomic-yes', '#label-autonomic-yes', ((data[1] / totalCount) * 100), data[1]);\r\n // this.setProgressBarValue('No', '#value-autonomic-no', '#label-autonomic-no', ((data[0] / totalCount) * 100), data[0]);\r\n // this.setProgressBarValue('Unknown', '#value-autonomic-unknown', '#label-autonomic-unknown', ((data[2] / totalCount) * 100), data[2]);\r\n //}\r\n\r\n private hideProgressBar(id: string): void {\r\n $('#' + id).removeClass('d-flex');\r\n $('#' + id).addClass('d-none');\r\n\r\n }\r\n private showProgressBar(id: string): void {\r\n $('#' + id).removeClass('d-none');\r\n $('#' + id).addClass('d-flex');\r\n }\r\n}\r\n\r\n","\r\n\r\nexport class Outcome {\r\n Id: number;\r\n Name: string;\r\n OrderBy: number;\r\n\r\n constructor(id: number, name: string, ordering: number) { this.Id = id; this.Name = name; this.OrderBy = ordering; }\r\n}\r\n\r\nexport class OutcomeEvent {\r\n\r\n StudyId: number;\r\n SiteNumber: number;\r\n AppendixId: number;\r\n OutcomeId: number;\r\n OutcomeStatusId: number;\r\n ReasonId: number;\r\n SubjectId: string;\r\n\r\n initialize(): void {\r\n\r\n //increment ReasonIds to be able to overload status logic\r\n if (this.ReasonId != undefined) { this.ReasonId += 100; }\r\n\r\n //if (this.SubjectId == \"VI0680001\") console.log(this);\r\n\r\n }\r\n}\r\n","import { Appendix } from \"../../../../../../shared/entities/scripts/appendix\";\r\nimport { Study } from \"../../../../../../shared/entities/scripts/study\";\r\nimport { FormatDate, GetDateFromString } from \"../../../../../../shared/scripts/date-functions\";\r\nimport { FormatNumberWithCommas } from \"../../../../../../shared/scripts/number-functions\";\r\nimport { RenderDateMDY } from \"../../../../../../shared/scripts/table-functions\";\r\nimport { SiteInfo } from \"../../../../sites/shared/sites.entities\";\r\nimport { OutcomeEvent } from \"./outcomes.entities\";\r\nimport { OutcomesLookup } from \"./outcomes.lookup\";\r\n\r\nexport class OutcomesDialog {\r\n static table: any = undefined;\r\n\r\n static Initialize() {\r\n OutcomesDialog.table = $('#outcomes-details-modal #dialog-details-table').DataTable({\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n searching: false,\r\n columns: [\r\n { data: \"Study\", title: \"Platform\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Appendix\", title: \"Trial\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"SiteNumber\", title: \"Site\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Outcome\", title: \"Outcome\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"OutcomeStatus\", title: \"Status\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Subject\", title: \"Participant\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Reason\", title: \"Reason Not Available\", className: \"text-left font-size12 nowrap\" },\r\n ]\r\n });\r\n }\r\n\r\n public static ShowDialog(outcomes: OutcomeEvent[]): void {\r\n //console.log(\"show\", outcomes);\r\n let showReasonsColumn: boolean = false;\r\n let tableRows: OutcomeDialogRow[] = outcomes.map((v: OutcomeEvent) => {\r\n if (!showReasonsColumn && v.OutcomeStatusId == 4) { showReasonsColumn = true; } //only when Not Available\r\n return new OutcomeDialogRow(v)\r\n });\r\n\r\n if (OutcomesDialog.table == undefined) { OutcomesDialog.Initialize(); }\r\n\r\n OutcomesDialog.table.column(6).visible(showReasonsColumn);\r\n\r\n OutcomesDialog.table.clear();\r\n OutcomesDialog.table.rows.add(tableRows).draw();\r\n\r\n $('#outcomes-details-modal').on('shown.bs.modal', () => {\r\n OutcomesDialog.table.columns.adjust();\r\n $('#outcomes-details-modal .dataTables_scrollBody').scrollTop(0);\r\n $('#outcomes-details-modal #modal-title').text(\"Number of Outcome Events = \" + FormatNumberWithCommas(outcomes.length + \"\"));\r\n //$('#visits-details-modal-count').text(visits.length);\r\n });\r\n\r\n $('#outcomes-details-modal').modal('show');\r\n\r\n }\r\n}\r\n\r\n\r\nexport class OutcomeDialogRow {\r\n Study: string;\r\n Appendix: string;\r\n SiteNumber: string;\r\n Subject: string;\r\n Outcome: string;\r\n //OutcomeOrdering: number;\r\n OutcomeStatus: string;\r\n Reason: string;\r\n\r\n constructor(event: OutcomeEvent) {\r\n this.Study = Study.GetStudyName(event.StudyId);\r\n this.Appendix = Appendix.GetAppendixName(event.AppendixId);\r\n this.SiteNumber = SiteInfo.FormatSiteNumber(event.SiteNumber);\r\n this.Subject = event.SubjectId;\r\n this.Outcome = OutcomesLookup.GetOutcome(event.OutcomeId);\r\n //this.VisitOrdering = VisitLookup.GetVisitOrdering(event.VisitId);\r\n this.OutcomeStatus = OutcomesLookup.GetOutcomeStatus(event.OutcomeStatusId);\r\n this.Reason = OutcomesLookup.GetReasonMissing(event.ReasonId);\r\n }\r\n}","import { plainToClass } from \"class-transformer\";\r\nimport { Appendix } from \"../../../../../../shared/entities/scripts/appendix\";\r\nimport { Study } from \"../../../../../../shared/entities/scripts/study\";\r\nimport { FilterOption } from \"../../../../../../shared/scripts/filtering/filtering.entities\";\r\nimport { SetActiveTab } from \"../../../../../../shared/scripts/tab-utilities\";\r\nimport { SiteInfo } from \"../../../../sites/shared/sites.entities\";\r\nimport { ISiteInfo } from \"../../../scripts/data-completeness.page\";\r\nimport { OutcomesDatatableView } from \"../outcomes-datatable-view/outcomes-datatable-view\";\r\nimport { OutcomesDatatableEntry } from \"../outcomes-datatable-view/outcomes-datatable.entities\";\r\nimport { OutcomesSummaryView } from \"../outcomes-summary-view/outcomes-summary-view\";\r\nimport { OutcomesSummarySeries } from \"../outcomes-summary-view/outcomes-summary.entities\";\r\nimport { Outcome, OutcomeEvent } from \"./outcomes.entities\";\r\nimport { OutcomesLookup } from \"./outcomes.lookup\";\r\n\r\n\r\nexport class OutcomesController {\r\n\r\n siteInfo: ISiteInfo;\r\n\r\n allOutcomes: Outcome[] = [];\r\n allOutcomeEvents: OutcomeEvent[] = [];\r\n\r\n summaryView: OutcomesSummaryView;\r\n datatableView: OutcomesDatatableView;\r\n\r\n constructor(siteInfo: ISiteInfo, outcomesTimingJson: any) {\r\n\r\n this.siteInfo = siteInfo;\r\n\r\n let foundOutcomeIds: number[] = [];\r\n this.allOutcomeEvents = plainToClass(OutcomeEvent, outcomesTimingJson);\r\n this.allOutcomeEvents.forEach((o: OutcomeEvent) => {\r\n o.initialize()\r\n\r\n if (!foundOutcomeIds.includes(o.OutcomeId)) {\r\n this.allOutcomes.push(new Outcome(o.OutcomeId, OutcomesLookup.GetOutcome(o.OutcomeId), OutcomesLookup.GetOutcomeOrdering(o.OutcomeId)));\r\n foundOutcomeIds.push(o.OutcomeId);\r\n }\r\n\r\n });\r\n this.allOutcomes.sort((a: Outcome, b: Outcome) => a.OrderBy - b.OrderBy);\r\n\r\n this.summaryView = new OutcomesSummaryView();\r\n this.datatableView = new OutcomesDatatableView();\r\n\r\n //console.log(this.allOutcomes);\r\n //console.log(this.allOutcomeEvents);\r\n }\r\n\r\n FilterUpdated(studyIds: number[], appendixIds: number[], siteIds: number[], outcomeIds: number[]): void {\r\n\r\n let byOutcomeSummaryMap: Map = new Map();\r\n let bySiteSummaryMap: Map = new Map();\r\n\r\n let noGroupingDataMap: Map = new Map();\r\n let groupingByOutcomeDataMap: Map = new Map();\r\n let groupingBySiteDataMap: Map = new Map();\r\n\r\n //for loop instead of forEach for performance given there could be tens of thousands of visit events across all trials\r\n for (let i = 0; i < this.allOutcomeEvents.length; ++i) {\r\n let outcome: OutcomeEvent = this.allOutcomeEvents[i];\r\n\r\n if (!studyIds.includes(outcome.StudyId) || !appendixIds.includes(outcome.AppendixId) ||\r\n !outcomeIds.includes(outcome.OutcomeId) || !siteIds.includes(outcome.SiteNumber)) continue;\r\n\r\n //Summary View Data\r\n let outcomeSeries: OutcomesSummarySeries;\r\n if (byOutcomeSummaryMap.has(outcome.OutcomeId)) { outcomeSeries = byOutcomeSummaryMap.get(outcome.OutcomeId); }\r\n else { outcomeSeries = new OutcomesSummarySeries(OutcomesLookup.GetOutcome(outcome.OutcomeId), \"outcome\", outcome.OutcomeId); }\r\n outcomeSeries.addOutcomeEvent(outcome);\r\n byOutcomeSummaryMap.set(outcome.OutcomeId, outcomeSeries);\r\n\r\n let siteSeries: OutcomesSummarySeries;\r\n if (bySiteSummaryMap.has(outcome.SiteNumber)) { siteSeries = bySiteSummaryMap.get(outcome.SiteNumber); }\r\n else { siteSeries = new OutcomesSummarySeries(this.siteInfo.GetSiteName(outcome.SiteNumber), \"site\", outcome.SiteNumber); }\r\n siteSeries.addOutcomeEvent(outcome);\r\n bySiteSummaryMap.set(outcome.SiteNumber, siteSeries);\r\n\r\n //Datatable View Data\r\n let dataHash: string;\r\n\r\n let noGroupingData: OutcomesDatatableEntry;\r\n dataHash = OutcomesDatatableEntry.getHash(\"none\", outcome);\r\n if (noGroupingDataMap.has(dataHash)) { noGroupingData = noGroupingDataMap.get(dataHash); }\r\n else { noGroupingData = new OutcomesDatatableEntry(Study.GetStudyName(outcome.StudyId), Appendix.GetAppendixName(outcome.AppendixId), this.siteInfo.GetSiteName(outcome.SiteNumber), SiteInfo.FormatSiteNumber(outcome.SiteNumber), OutcomesLookup.GetOutcome(outcome.OutcomeId)); }\r\n noGroupingData.addOutcomeEvent(outcome);\r\n noGroupingDataMap.set(dataHash, noGroupingData);\r\n\r\n let outcomeGroupingData: OutcomesDatatableEntry;\r\n dataHash = OutcomesDatatableEntry.getHash(\"by-outcome\", outcome);\r\n if (groupingByOutcomeDataMap.has(dataHash)) { outcomeGroupingData = groupingByOutcomeDataMap.get(dataHash); }\r\n else { outcomeGroupingData = new OutcomesDatatableEntry(Study.GetStudyName(outcome.StudyId), Appendix.GetAppendixName(outcome.AppendixId), \"All Sites\", \"All Sites\", OutcomesLookup.GetOutcome(outcome.OutcomeId)); }\r\n outcomeGroupingData.addOutcomeEvent(outcome);\r\n groupingByOutcomeDataMap.set(dataHash, outcomeGroupingData);\r\n\r\n let siteGroupingData: OutcomesDatatableEntry;\r\n dataHash = OutcomesDatatableEntry.getHash(\"by-site\", outcome);\r\n if (groupingBySiteDataMap.has(dataHash)) { siteGroupingData = groupingBySiteDataMap.get(dataHash); }\r\n else { siteGroupingData = new OutcomesDatatableEntry(Study.GetStudyName(outcome.StudyId), Appendix.GetAppendixName(outcome.AppendixId), this.siteInfo.GetSiteName(outcome.SiteNumber), SiteInfo.FormatSiteNumber(outcome.SiteNumber), \"All Outcomes (n=\" + Appendix.GetOutcomeCount(outcome.AppendixId) + \")\"); }\r\n siteGroupingData.addOutcomeEvent(outcome);\r\n groupingBySiteDataMap.set(dataHash, siteGroupingData);\r\n }\r\n\r\n let seriesByVisit: OutcomesSummarySeries[] = [...byOutcomeSummaryMap.values()];\r\n seriesByVisit.sort((a: OutcomesSummarySeries, b: OutcomesSummarySeries) => a.sortValue - b.sortValue);\r\n\r\n let seriesBySite: OutcomesSummarySeries[] = [...bySiteSummaryMap.values()];\r\n seriesBySite.sort((a: OutcomesSummarySeries, b: OutcomesSummarySeries) => b.sortValue - a.sortValue);\r\n\r\n this.summaryView.UpdateData(seriesByVisit, seriesBySite);\r\n\r\n\r\n let tableEntriesNone: OutcomesDatatableEntry[] = [...noGroupingDataMap.values()];\r\n for (let i: number = 0; i < tableEntriesNone.length; ++i) { tableEntriesNone[i].initialize(); }\r\n //sort it somehow\r\n\r\n let tableEntriesByOutcome: OutcomesDatatableEntry[] = [...groupingByOutcomeDataMap.values()];\r\n for (let i: number = 0; i < tableEntriesByOutcome.length; ++i) { tableEntriesByOutcome[i].initialize(); }\r\n\r\n let tableEntriesBySite: OutcomesDatatableEntry[] = [...groupingBySiteDataMap.values()];\r\n for (let i: number = 0; i < tableEntriesBySite.length; ++i) { tableEntriesBySite[i].initialize(); }\r\n\r\n //console.log(noGroupingDataMap);\r\n\r\n this.datatableView.UpdateData(tableEntriesNone, tableEntriesByOutcome, tableEntriesBySite);\r\n }\r\n\r\n setView(newView: string): void {\r\n\r\n //console.log(\"outcomes.setView\", newView);\r\n\r\n if (newView == \"view-summary\") { SetActiveTab(\"outcomes-tabs\", \"outcomes-summary-content\"); this.summaryView.SetView(\"outcomes-summary\"); }\r\n else if (newView == \"view-datatable\") { SetActiveTab(\"outcomes-tabs\", \"outcomes-datatable-content\"); this.datatableView.SetView(\"outcomes-datatable-none\"); }\r\n else {\r\n this.summaryView.SetView(newView);\r\n this.datatableView.SetView(newView);\r\n }\r\n }\r\n\r\n exportTable(): void {\r\n this.datatableView.exportTable();\r\n }\r\n}","import { SumArrayElements } from \"../../../../shared/scripts/array-functions\";\r\nimport { ChartDataPoint } from \"./enrollment.chart-controller\";\r\nimport { AggregatedParticipants, Participant } from \"./enrollment.entities\";\r\nimport { IEnrollmentPageData } from \"./enrollment.page\";\r\n\r\n\r\n\r\nvar Highcharts = require('highcharts/highcharts');\r\n//require('highcharts/highcharts-more')(Highcharts);\r\nrequire('highcharts/modules/solid-gauge')(Highcharts);\r\n\r\nexport class EnrollmentGauges {\r\n\r\n parent: IEnrollmentPageData;\r\n\r\n exIntGauge: any;\r\n exIntChart: any;\r\n cogDysGauge: any;\r\n cogDysChart: any;\r\n autoGauge: any;\r\n autoChart: any;\r\n\r\n constructor(parent: IEnrollmentPageData) {\r\n this.parent = parent;\r\n\r\n let cardOptions: any[] = [{ buttonId: '#toggle-sxcluster-exint', cardId: '#card-sxcluster-exint' },\r\n { buttonId: '#toggle-sxcluster-cogdys', cardId: '#card-sxcluster-cogdys' },\r\n { buttonId: '#toggle-sxcluster-auto', cardId: '#card-sxcluster-auto' }];\r\n\r\n cardOptions.forEach((option: any) => {\r\n $(option.buttonId).on('click', (e) => {\r\n e.preventDefault();\r\n $(option.cardId).toggleClass('flipped');\r\n if ($(option.cardId).hasClass('flipped')) {\r\n $(option.buttonId + \" .line-chart-icon\").removeClass('fa-chart-line');\r\n $(option.buttonId + \" .line-chart-icon\").addClass('fa-bars-progress');\r\n }\r\n else {\r\n $(option.buttonId + \" .line-chart-icon\").removeClass('fa-bars-progress');\r\n $(option.buttonId + \" .line-chart-icon\").addClass('fa-chart-line'); \r\n }\r\n })\r\n });\r\n\r\n this.exIntGauge = this.initializeGauge(\"sxcluster-exint-gauge\", 0, 300, \"Participants\");\r\n this.cogDysGauge = this.initializeGauge(\"sxcluster-cogdys-gauge\", 0, 300, \"Participants\");\r\n this.autoGauge = this.initializeGauge(\"sxcluster-auto-gauge\", 0, 300, \"Participants\");\r\n\r\n this.exIntChart = this.initializeChart(\"sxcluster-exint-chart\");\r\n this.cogDysChart = this.initializeChart(\"sxcluster-cogdys-chart\");\r\n this.autoChart = this.initializeChart(\"sxcluster-auto-chart\");\r\n }\r\n\r\n UpdateData(aggregatedParticipants: AggregatedParticipants): void {\r\n //this.updateSymptomClusterGauges(aggregatedParticipants.ClusterExInt[1], aggregatedParticipants.ClusterCogn[1], aggregatedParticipants.ClusterAuto[1]);\r\n\r\n this.exIntGauge.series[0].points[0].update(aggregatedParticipants.ClusterExInt[1]);\r\n this.cogDysGauge.series[0].points[0].update(aggregatedParticipants.ClusterCogn[1]);\r\n this.autoGauge.series[0].points[0].update(aggregatedParticipants.ClusterAuto[1]);\r\n //console.log(this.exIntGauge);\r\n\r\n this.exIntChart.series[0].setData(this.createChartData(this.parent.selectedParticipants.filter((p: Participant) => p.VitSymExInt == 1)));\r\n this.exIntChart.redraw();\r\n\r\n this.cogDysChart.series[0].setData(this.createChartData(this.parent.selectedParticipants.filter((p: Participant) => p.VitSymCogDys == 1)));\r\n this.cogDysChart.redraw();\r\n\r\n this.autoChart.series[0].setData(this.createChartData(this.parent.selectedParticipants.filter((p: Participant) => p.VitSymAutoDys == 1)));\r\n this.autoChart.redraw();\r\n }\r\n\r\n private initializeGauge(id: string, current: number, target: number, totalLabel: string): any {\r\n\r\n let gauge: any = Highcharts.chart(id, {\r\n chart: {\r\n type: 'solidgauge'\r\n },\r\n title: null,\r\n credits: { enabled: false },\r\n pane: {\r\n center: ['50%', '90%'],\r\n size: '170%',\r\n startAngle: -90,\r\n endAngle: 90,\r\n background: {\r\n backgroundColor: '#ffc757',\r\n innerRadius: '70%',\r\n outerRadius: '100%',\r\n shape: 'arc'\r\n }\r\n },\r\n exporting: {\r\n enabled: false\r\n },\r\n tooltip: {\r\n enabled: false\r\n },\r\n yAxis: {\r\n lineWidth: 0,\r\n tickWidth: 0,\r\n minorTickInterval: null,\r\n tickAmount: 2,\r\n tickPositions: [0, target],\r\n labels: {\r\n y: 20,\r\n distance: -18,\r\n style: {\r\n fontSize: '14px'\r\n }\r\n },\r\n min: 0,\r\n max: target,\r\n title: {\r\n text: null,\r\n //y: -70\r\n }\r\n },\r\n plotOptions: {\r\n solidgauge: {\r\n dataLabels: {\r\n y: 50,\r\n borderWidth: 0,\r\n useHTML: true\r\n }\r\n }\r\n },\r\n series: [{\r\n name: 'Participants',\r\n data: [{\r\n y: current,\r\n innerRadius: '70%'\r\n }],\r\n dataLabels: {\r\n formatter: function () {\r\n let label: string = '';\r\n label += '' + Highcharts.numberFormat(this.y, 0, '', ',') + '
';\r\n label += 'Participants
';\r\n label += '(' + Highcharts.numberFormat((this.y / 300.0) * 100, 0, '', ',') + '%)';\r\n label += '
';\r\n return label;\r\n }\r\n\r\n //format:\r\n // '' +\r\n // '{point.y:,.0f}
' +\r\n // '' + totalLabel + '' +\r\n // '
',\r\n },\r\n tooltip: {\r\n valueSuffix: ' Total ' + totalLabel\r\n }\r\n }]\r\n });\r\n return gauge;\r\n }\r\n\r\n private initializeChart(id: string): any {\r\n\r\n let chart = Highcharts.chart(id, {\r\n chart: {\r\n backgroundColor: 'white',\r\n },\r\n credits: { enabled: false },\r\n title: { text: null },\r\n xAxis: {\r\n type: 'datetime',\r\n //max: maxDate,\r\n showLastLabel: true,\r\n labels: {\r\n formatter: function () {\r\n return Highcharts.dateFormat('%e %b %Y', this.value);\r\n }\r\n }\r\n },\r\n yAxis: {\r\n //max: maxEnrollment,\r\n title: {\r\n text: 'Participants'\r\n },\r\n labels: {\r\n formatter: function () {\r\n return Highcharts.numberFormat(this.value, 0, '', ',');\r\n }\r\n },\r\n plotLines: [{\r\n value: 300,\r\n dashStyle: 'longdash',\r\n label: {\r\n text: 'Target',\r\n y: -5\r\n },\r\n zIndex: 5\r\n }],\r\n max: 350\r\n },\r\n plotOptions: {\r\n line: {\r\n stickyTracking: false,\r\n findNearestPointBy: 'xy',\r\n marker: {\r\n enabled: false\r\n }\r\n },\r\n },\r\n tooltip: {\r\n xDateFormat: '%e %b %Y',\r\n shared: false\r\n },\r\n legend: {\r\n enabled: false,\r\n //symbolWidth: 0\r\n },\r\n series: [{ name: \"Actual\", color: \"#152850\", data:[] }] //RECOVER dark blue\r\n });\r\n\r\n return chart;\r\n }\r\n\r\n private createChartData(participants: Participant[]): ChartDataPoint[] {\r\n\r\n let data: ChartDataPoint[] = [];\r\n\r\n if (participants.length == 0) return data;\r\n\r\n let dayMonthDictionary: Map = new Map(); //value: array of counts by study (to facilitate rate computations)\r\n\r\n participants.forEach((p: Participant) => {\r\n let studyCounts: number[] = [];\r\n let dateOfInterest: number = p.DateRandomized.getTime();\r\n\r\n if (dateOfInterest > 0) {\r\n if (dayMonthDictionary.has(dateOfInterest)) {\r\n studyCounts = dayMonthDictionary.get(dateOfInterest);\r\n }\r\n if (studyCounts[p.StudyId] == undefined) studyCounts[p.StudyId] = 0;\r\n studyCounts[p.StudyId] += 1;\r\n dayMonthDictionary.set(dateOfInterest, studyCounts);\r\n }\r\n });\r\n\r\n //add up cummulative enrollment over time\r\n let lastDate: number = -1;\r\n let countToDate: number = 0;\r\n dayMonthDictionary.forEach((studyCounts: number[], date: number) => {\r\n countToDate += SumArrayElements(studyCounts);\r\n\r\n if (date != lastDate) {\r\n data.push(new ChartDataPoint(date, countToDate));\r\n lastDate = date;\r\n }\r\n });\r\n\r\n return data;\r\n }\r\n\r\n}","//import { UserExperience } from \"../../../auth/pages/profile/scripts/expertise.objects\";\r\nimport { FormatStringByWordLength, DoArraysOverlap, Split, Join } from \"../../../shared/scripts/array-functions\";\r\n\r\nexport class UserDirectoryEntity {\r\n\r\n FirstName: string;\r\n LastName: string;\r\n ProjectRole: string;\r\n Email: string;\r\n Institution: string = \"\";\r\n Affiliations: string = \"None Selected\";\r\n StudyAccess: string = \"None Selected\";\r\n\r\n DisplayName: string;\r\n SortName: string;\r\n DisplayEmail: string = \"\";\r\n DisplayAffiliation: string = \"\";\r\n DisplayStudy: string = \"\";\r\n DisplayInstitution: string = \"\";\r\n DisplayRole: string = \"\";\r\n AffiliationList: string[];\r\n StudyList: string[];\r\n HiddenFilter: string = \"\";\r\n\r\n constructor() { }\r\n\r\n initialize(): void {\r\n\r\n //console.log(this);\r\n\r\n this.DisplayName = this.FirstName + \" \" + this.LastName;\r\n this.DisplayName = FormatStringByWordLength(this.DisplayName, 4);\r\n this.SortName = this.LastName + \" \" + this.FirstName;\r\n\r\n if (this.Email?.length > 0) {\r\n //this.DisplayEmail = \"\" + this.Email.toLowerCase() + \"\";\r\n this.DisplayEmail = \"\" + this.Email.toLowerCase() + \"\";\r\n }\r\n\r\n\r\n //if (this.Affiliation == \"SAG\") { this.Affiliation = \"Scientific Advisory Group (SAG)\"; }\r\n\r\n this.AffiliationList = Split(this.Affiliations, \"|\");\r\n this.DisplayAffiliation = Join(this.AffiliationList, \", \", true, true);\r\n\r\n this.StudyList = Split(this.StudyAccess, \"|\");\r\n this.DisplayStudy = Join(this.StudyList, \", \", true, true);\r\n\r\n this.DisplayInstitution = FormatStringByWordLength(this.Institution, 4);\r\n this.DisplayRole = FormatStringByWordLength(this.ProjectRole, 4);\r\n }\r\n\r\n //matchesFilter(affiliation: any, institute: any, country: any, basicScienceExpertise: any, diagnosticExpertise: any, helminthExpertise: any, protozoanExpertise: any, bacteriaExpertise: any): boolean {\r\n matchesFilter(affiliation: any, study: any, institute: any): boolean {\r\n\r\n //console.log(affiliation, this.Affiliation);\r\n\r\n if (!DoArraysOverlap(affiliation, this.AffiliationList)) { return false; }\r\n if (!DoArraysOverlap(study, this.StudyList)) { return false; }\r\n if (!DoArraysOverlap(institute, this.Institution)) { return false; }\r\n\r\n return true;\r\n }\r\n}","\r\n\r\n\r\nexport class ContractsLookup {\r\n\r\n //static GetPrimeContract(id: number): string {\r\n // switch (id) {\r\n // case (1): return \"Infrastructure\";\r\n // case (2): return \"VITAL\";\r\n // case (3): return \"NEURO\";\r\n // case (4): return \"SLEEP\";\r\n // case (5): return \"AUTONOMIC\";\r\n // case (6): return \"ENERGIZE\";\r\n // default: return \"Unknown\";\r\n // }\r\n //}\r\n\r\n static GetContractType(id: number): string {\r\n switch (id) {\r\n case (1): return \"CDA\";\r\n case (2): return \"DUA\";\r\n case (3): return \"FUA\";\r\n case (4): return \"Funding Letter\";\r\n case (5): return \"LOI\";\r\n case (6): return \"License Agreement\";\r\n case (7): return \"MOU\";\r\n case (8): return \"MTA\";\r\n case (9): return \"Master Agreement\";\r\n case (10): return \"Member Agreement\";\r\n case (11): return \"Other\";\r\n case (12): return \"Site Agreement\";\r\n case (13): return \"Site Master Agreement\";\r\n case (14): return \"Support Services/Subcontract\";\r\n case (15): return \"Term Letter\";\r\n case (16): return \"Protocol Working Group\";\r\n case (17): return \"Study Coordinator Group\";\r\n case (18): return \"Site Study Contracts\";\r\n case (19): return \"Pre-Screening Agreement\";\r\n case (20): return \"Study Addendum - Site\";\r\n case (21): return \"Protocol Working Groups\";\r\n default: return \"Unknown\";\r\n }\r\n }\r\n\r\n static GetContractStatus(id: number): string {\r\n switch (id) {\r\n case (1): return \"DCRI e-IAF\";\r\n case (2): return \"Notified\";\r\n case (3): return \"In Draft-DCRI Contracts\";\r\n case (4): return \"In Neg - Risk Management\";\r\n case (5): return \"In Neg-DCRI\";\r\n case (6): return \"In Neg-DCRI Contracts\";\r\n case (7): return \"In Neg-Duke\";\r\n case (8): return \"In Neg-Ext Pty\";\r\n case (9): return \"In Neg-ORA\";\r\n case (10): return \"In Neg-ORC\";\r\n case (11): return \"In Neg-Procurement\";\r\n case (12): return \"In Neg-Sponsor\";\r\n case (13): return \"On Hold\";\r\n case (14): return \"On Hold-Cart\";\r\n case (15): return \"On Hold-Ext Pty\";\r\n case (16): return \"On Hold-K complete\";\r\n case (17): return \"On Hold-PL Attention Needed\";\r\n case (18): return \"On Hold-Pending Budget/Scope\";\r\n case (19): return \"On Hold-Pending SPA PA\";\r\n case (20): return \"In Sig - ORA\";\r\n case (21): return \"In Sig - ORC\";\r\n case (22): return \"In Sig - Procurement\";\r\n case (23): return \"In Sig-CM\";\r\n case (24): return \"In Sig-DCRI Contracts\";\r\n case (25): return \"In Sig-Ext Pty\";\r\n case (26): return \"Signed K Recd-DCRI Contracts\";\r\n case (27): return \"Signed K Recd-Duke\";\r\n case (28): return \"Signed K Recd-Ext. Party\";\r\n case (29): return \"Signed K Recd-ORA\";\r\n case (30): return \"Signed K Recd-ORC\";\r\n case (31): return \"Signed K Recd-Procurement\";\r\n case (32): return \"Executed-Contracts\";\r\n case (33): return \"Executed-Ext Pty\";\r\n case (34): return \"Executed-ORA\";\r\n case (35): return \"Executed-ORC\";\r\n case (36): return \"Executed-Procurement\";\r\n case (37): return \"Executed-Sponsor\";\r\n case (38): return \"Executed\";\r\n case (39): return \"Waiver Received\";\r\n case (40): return \"Withdrawn\";\r\n case (41): return \"In Draft-Ext Pty\";\r\n case (42): return \"Requested/Pending\";\r\n case (43): return \"In Sig - PI\";\r\n case (44): return \"Executed-PI\";\r\n case (45): return \"In Sig-Sponsor\";\r\n case (46): return \"In Sig-DCRI\";\r\n case (47): return \"Executed-CFO\";\r\n case (48): return \"CM Peer Review\";\r\n case (49): return \"On Hold-Pending GPS\";\r\n case (50): return \"Signed K Recd-Sponsor\";\r\n case (51): return \"On Hold-Pending Site IRB\";\r\n case (52): return \"Unknown\";\r\n case (53): return \"Signed K Recd-CFO\";\r\n default: return \"Unknown\";\r\n }\r\n }\r\n\r\n static GetContractStatusGroup(id: number): string {\r\n switch (id) {\r\n case (1): return \"Requested\";\r\n case (2): return \"Draft\";\r\n case (3): return \"Negotiation\";\r\n case (4): return \"On Hold\";\r\n case (5): return \"Signature\";\r\n case (6): return \"Waiver Received\";\r\n case (7): return \"Fully Executed\";\r\n case (8): return \"Withdrawn\";\r\n case (9): return \"Other\";\r\n default: return \"Unknown\";\r\n }\r\n }\r\n}","\r\n//Durstenfeld Approach: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm\r\nexport function ShuffleArray(array: any[]) {\r\n for (let i = array.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [array[i], array[j]] = [array[j], array[i]];\r\n }\r\n}\r\n\r\nexport function SplitAndJoinDelimited(input: string, splitDelimiter: string, outputDelimiter: string, sort: boolean = false, nowrap: boolean = false): string {\r\n if (input.trim().length == 0) return \"\";\r\n else {\r\n let list: string[] = input.split(splitDelimiter);\r\n if (sort) { list.sort(); }\r\n let output: string = \"\";\r\n list.forEach((item: string, index: number) => {\r\n if (index > 0) output += outputDelimiter;\r\n if (nowrap) output += \"\";\r\n output += item;\r\n if (nowrap) output += \"\";\r\n });\r\n return output;\r\n }\r\n}\r\n\r\nexport function SplitAndJoinContained(input: string, splitDelimiter: string, outputPrefixDelimiter: string, outputSuffixDelimiter: string, sort: boolean = false, nowrap: boolean = false): string {\r\n if (input.trim().length == 0) return \"\";\r\n else {\r\n let list: string[] = input.split(splitDelimiter);\r\n if (sort) { list.sort(); }\r\n let output: string = \"\";\r\n list.forEach((item: string, index: number) => {\r\n output += outputPrefixDelimiter;\r\n if (nowrap) output += \"\";\r\n output += item;\r\n if (nowrap) output += \"\";\r\n output += outputSuffixDelimiter;\r\n });\r\n return output;\r\n }\r\n}\r\n\r\nexport function Split(input: string, delimiter: string): string[] {\r\n if (input.trim().length == 0) return [];\r\n else {\r\n let parts: string[] = input.split(delimiter);\r\n parts = parts.map((p: string) => p.trim());\r\n return parts;\r\n }\r\n}\r\n\r\nexport function Join(list: string[], outputDelimiter: string, sort: boolean, nowrap: boolean = false): string {\r\n if (sort) { list.sort(); }\r\n let output: string = \"\";\r\n list.forEach((item: string, index: number) => {\r\n if (index > 0) output += outputDelimiter;\r\n if (nowrap) output += \"\";\r\n output += item;\r\n if (nowrap) output += \"\";\r\n });\r\n return output;\r\n}\r\n\r\nexport function Unique(values: string[], sort: boolean, lastValues: string[] = []): string[] {\r\n\r\n values = values.map((val: string) => {\r\n if (val.startsWith(\"Other\")) return \"Other\";\r\n else return val;\r\n });\r\n\r\n let unique: string[] = values.filter((n, i) => values.indexOf(n) === i);\r\n if (sort) {\r\n lastValues = lastValues.map((v: string) => v.toLowerCase());\r\n\r\n unique = unique.sort((a: string, b: string) => {\r\n a = a.toLowerCase();\r\n b = b.toLowerCase();\r\n\r\n if (lastValues.length == 0) { return (a > b ? 1 : -1); }\r\n else {\r\n let isALastValue: boolean = false;\r\n let isBLastValue: boolean = false;\r\n\r\n lastValues.forEach((v: string) => {\r\n if (a.startsWith(v)) { isALastValue = true; }\r\n if (b.startsWith(v)) { isBLastValue = true; }\r\n });\r\n\r\n if (!isALastValue && !isBLastValue) { return (a > b ? 1 : -1); }\r\n else if (isALastValue && !isBLastValue) {\r\n return 1;\r\n }\r\n else if (!isALastValue && isBLastValue) {\r\n return -1;\r\n }\r\n else {\r\n return (a > b ? 1 : -1);\r\n }\r\n }\r\n\r\n //if (a.startsWith(v) && b.startsWith(v)) { return (a > b ? 1 : -1); }\r\n //else if (a.startsWith(v)) { return 1; }\r\n //else if (b.startsWith(v)) { return -1; }\r\n //else return (a > b ? 1 : -1);\r\n\r\n\r\n });\r\n\r\n //unique.sort();\r\n }\r\n return unique;\r\n}\r\n\r\nexport function FormatStringByWordLength(value: string, wordsOnLine: number): string {\r\n\r\n //console.log(value);\r\n\r\n if (value == undefined) return \"\";\r\n\r\n let parts: string[] = value.split(\" \");\r\n let output: string = \"\";\r\n for (let i = 0; i < parts.length; ++i) {\r\n output += parts[i] + \" \";\r\n if (i == (wordsOnLine - 1)) {\r\n output += \"\";\r\n }\r\n }\r\n\r\n if (parts.length < (wordsOnLine - 1)) { output += \"\"; }\r\n return output.trim();\r\n}\r\n\r\nexport function DoArraysOverlap(array1: any, array2: any): boolean {\r\n if (array1 == undefined || array2 == undefined) return false;\r\n return array1.some(r => array2.includes(r));\r\n}\r\n\r\nexport function NoWordWrap(input: string): string {\r\n return \"\" + input + \"\";\r\n}\r\n\r\nexport function CheckArrayStartsWith(array: string[], startsWithValue: string, appendValue: string): string[] {\r\n\r\n let includesValue: number = array.findIndex((value) => value.startsWith(startsWithValue));\r\n\r\n if (includesValue == -1) { return array; }\r\n else { array.push(appendValue); return array; }\r\n}\r\n\r\nexport function RemoveElementFromArray(array: any[], valueToRemove: any): any[] {\r\n const index = array.indexOf(valueToRemove, 0);\r\n if (index > -1) {\r\n array = array.splice(index, 1);\r\n }\r\n return array;\r\n}\r\n\r\nexport function SumArrayElements(array: number[]): number {\r\n let sum: number = 0;\r\n for (let i = 0; i < array.length; ++i) {\r\n if (array[i] != undefined) { sum += array[i]; }\r\n }\r\n\r\n //console.log(sum, array);\r\n return sum;\r\n}","import { PopoverOption } from \"bootstrap\";\r\nimport { SiteMetricDetail, SiteMetricSummary } from \"../../scripts/site-activation.entities\";\r\n\r\n\r\nexport type SiteCardData = {\r\n studyId: number;\r\n studyName: string;\r\n metricId: number;\r\n metricName: string;\r\n inPlayId: number;\r\n orderBy: number;\r\n yellowDays?: number;\r\n redDays?: number;\r\n completed?: SiteMetricSummary;\r\n inProgress?: SiteMetricSummary;\r\n sites?: SiteMetricDetail[];\r\n}\r\n\r\nexport class DashboardCard extends HTMLElement {\r\n cardData = {} as SiteCardData;\r\n detailsTabState = true;\r\n isOpen = false;\r\n yellowDays: any;\r\n redDays: any;\r\n studybg: string;\r\n studybglight: string;\r\n studyborder: string;\r\n inProgress: any;\r\n completed: any;\r\n inProgressCount: any;\r\n completedCount: any;\r\n stoplightMax: any;\r\n minDays: any;\r\n plotMin: number;\r\n maxDays: any;\r\n plotMax: number;\r\n greenSites: any;\r\n yellowSites: any;\r\n redSites: any;\r\n completedSites: any;\r\n inprogressTab: any;\r\n inprogressTabContent: any;\r\n completedTab: any;\r\n completedTabContent: any;\r\n\r\n constructor() {\r\n super();\r\n }\r\n\r\n connectedCallback() {\r\n\r\n this.initializeData();\r\n\r\n this.render();\r\n\r\n const summary = this.querySelector('.dsa-summary');\r\n const details = $(this.querySelector('.dsa-details'));\r\n\r\n summary.addEventListener('click', () => {\r\n\r\n this.isOpen = !details.hasClass('show')\r\n details.collapse('toggle');\r\n\r\n if (this.isOpen) {\r\n summary.classList.add('open');\r\n } else {\r\n summary.classList.remove('open');\r\n }\r\n });\r\n\r\n // Only show popover if there is data\r\n if (this.inProgressCount > 0) {\r\n $(this.querySelector('.dsa-timeline-plot-top')).popover(\r\n this.getPopoverOptions(this.inProgress, 'In-Progress')\r\n );\r\n }\r\n\r\n // Only show popover if there is data\r\n if (this.completedCount > 0) {\r\n $(this.querySelector('.dsa-timeline-plot-bottom')).popover(\r\n this.getPopoverOptions(this.completed, 'Completed')\r\n );\r\n }\r\n\r\n this.inprogressTab = this.querySelector('.inprogress-tab');\r\n this.inprogressTabContent = this.querySelector('.inprogress-tab-content');\r\n this.completedTab = this.querySelector('.completed-tab');\r\n this.completedTabContent = this.querySelector('.completed-tab-content');\r\n\r\n this.inprogressTab.addEventListener('click', () => {\r\n this.detailsTabState = true;\r\n this.updatedDetailsTabs();\r\n });\r\n\r\n this.completedTab.addEventListener('click', () => {\r\n this.detailsTabState = false;\r\n this.updatedDetailsTabs();\r\n });\r\n\r\n $(this.querySelectorAll('[data-toggle=\"tooltip\"]')).tooltip();\r\n\r\n }\r\n\r\n render() {\r\n\r\n const inProgressPlotMarkup = `\r\n \r\n ${(this.inProgressCount > 0) ? this.getPlotMarkup(this.inProgress, true) : ''}\r\n
\r\n `;\r\n\r\n const completedPlotMarkup = `\r\n \r\n ${(this.completedCount > 0) ? this.getPlotMarkup(this.completed, false) : ''}\r\n
\r\n `;\r\n\r\n const inProgressPercent = (this.inProgressCount / (this.inProgressCount + this.completedCount)) * 100 || 0;\r\n const completedPercent = (this.completedCount / (this.inProgressCount + this.completedCount)) * 100 || 0;\r\n\r\n this.innerHTML = `\r\n \r\n
\r\n \r\n
\r\n
\r\n
In Progress Sites
\r\n
\r\n ${this.getStoplightMarkup()}\r\n
\r\n
\r\n\r\n
\r\n
\r\n ${this.inProgressCount} (${inProgressPercent.toFixed()}%) Sites
In-Progress\r\n
\r\n ${inProgressPlotMarkup}\r\n
${this.maxDays} days
\r\n
\r\n
\r\n
\r\n ${this.getCompletedLabelMarkup(completedPercent)}\r\n
\r\n ${completedPlotMarkup}\r\n
\r\n\r\n
\r\n
\r\n
\r\n
\r\n - \r\n \r\n
\r\n - \r\n \r\n
\r\n
\r\n\r\n
\r\n
\r\n
\r\n ${this.getSiteTable(this.redSites, `Needs Attention (More than ${this.redDays} days)`, 'dsa-red')}\r\n
\r\n
\r\n ${this.getSiteTable(this.yellowSites, `Worth Watching (${this.yellowDays}-${this.redDays} days)`, 'dsa-yellow')}\r\n
\r\n
\r\n ${this.getSiteTable(this.greenSites, `On Track (Less than ${this.yellowDays} days)`, 'dsa-green')}\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n ${this.getSiteTable(this.completedSites, `Completed`, `${this.studybglight} ${this.studyborder}`)}\r\n
\r\n
\r\n
\r\n
\r\n
`;\r\n\r\n }\r\n\r\n setData(data, maxSites, maxDays, minDays) {\r\n this.cardData = data;\r\n this.stoplightMax = maxSites;\r\n this.plotMax = maxDays;\r\n this.plotMin = minDays;\r\n }\r\n\r\n initializeData() {\r\n this.yellowDays = this.cardData.yellowDays || 9999;\r\n this.redDays = this.cardData.redDays || 9999;\r\n\r\n this.studybg = `study-${this.cardData.studyId}-bg`;\r\n this.studybglight = `study-${this.cardData.studyId}-bg-light`;\r\n this.studyborder = `study-${this.cardData.studyId}-border`;\r\n\r\n this.inProgress = this.cardData.inProgress || {};\r\n this.completed = this.cardData.completed || {};\r\n\r\n this.inProgressCount = this.inProgress.NumberOfSites || 0;\r\n this.completedCount = this.completed.NumberOfSites || 0;\r\n\r\n this.stoplightMax = this.stoplightMax || this.inProgressCount;\r\n\r\n this.minDays = this.plotMin || Math.min(this.inProgress.MinValue, this.completed.MinValue);\r\n this.maxDays = this.plotMax || Math.max(this.inProgress.MaxValue, this.completed.MaxValue);\r\n\r\n this.greenSites = this.getSitesDesc(Number.MIN_SAFE_INTEGER, this.yellowDays, 0);\r\n this.yellowSites = this.getSitesDesc(this.yellowDays, this.redDays, 0);\r\n this.redSites = this.getSitesDesc(this.redDays, Number.MAX_VALUE, 0);\r\n this.completedSites = this.getSitesDesc(Number.MIN_SAFE_INTEGER, Number.MAX_VALUE, 1);\r\n\r\n }\r\n\r\n getPlotMarkup(data, showYellowRed=false) {\r\n\r\n const minPercent = this.getPlotPercent(data.MinValue);\r\n const maxPercent = 100 - this.getPlotPercent(data.MaxValue);\r\n const q1Percent = this.getPlotPercent(data.Q1Value);\r\n const q3Percent = 100 - this.getPlotPercent(data.Q3);\r\n const medianPercent = this.getPlotPercent(data.MedianValue);\r\n const meanPercent = this.getPlotPercent(data.MeanValue);\r\n\r\n const yellowPercent = this.getPlotPercent(this.yellowDays);\r\n const redPercent = this.getPlotPercent(this.redDays);\r\n\r\n const yellowMarkup = (showYellowRed && this.yellowDays && this.yellowDays < this.maxDays && this.yellowDays > this.minDays) ? `` : '';\r\n const redMarkup = (showYellowRed && this.redDays && this.redDays < this.maxDays && this.redDays > this.minDays) ? `` : '';\r\n\r\n return `\r\n ${yellowMarkup}\r\n ${redMarkup}\r\n \r\n
${(data.NumberOfSites === 1) ? '' : data.Min}
\r\n
${(data.NumberOfSites === 1) ? '' : data.Max}
\r\n
\r\n \r\n \r\n \r\n
\r\n \r\n
\r\n ${data.Mean}\r\n
\r\n
\r\n `;\r\n }\r\n\r\n getPlotPercent(value) {\r\n return Math.round(((value - this.minDays) / (this.maxDays - this.minDays)) * 100);\r\n }\r\n\r\n getStoplightPercent(value) {\r\n return Math.round((value / this.stoplightMax) * 100);\r\n }\r\n\r\n getStoplightMarkup() {\r\n\r\n const greenCount = this.cardData.sites.filter((site) => ((site.CompletedId === 0) && (site.DaysValue < this.yellowDays))).length;\r\n const yellowCount = this.cardData.sites.filter((site) => ((site.CompletedId === 0) && (site.DaysValue >= this.yellowDays) && (site.DaysValue < this.redDays))).length;\r\n const redCount = this.cardData.sites.filter((site) => ((site.CompletedId === 0) && (site.DaysValue >= this.redDays))).length;\r\n\r\n const greenPercent = this.getStoplightPercent(greenCount);\r\n const yellowPercent = this.getStoplightPercent(yellowCount);\r\n const redPercent = this.getStoplightPercent(redCount);\r\n\r\n return `\r\n ${this.getStoplightBandMarkup('green', greenPercent, greenCount)}\r\n ${this.getStoplightBandMarkup('yellow', yellowPercent, yellowCount)}\r\n ${this.getStoplightBandMarkup('red', redPercent, redCount)}\r\n `;\r\n }\r\n\r\n getStoplightBandMarkup(color, percent, count) {\r\n const label = `${count} Site${(count === 1) ? '' : 's'}`;\r\n return (percent > 0) ? `\r\n \r\n ` : '';\r\n }\r\n\r\n getCompletedLabelMarkup(completedPercent) {\r\n if (this.cardData.metricId === 16) return ''; // Don't show Completed for \"Time Since Most Recent Randomization\"\r\n return `${this.completedCount} (${completedPercent.toFixed()}%) Sites
Completed`;\r\n }\r\n\r\n getSitesDesc(startDays, endDays, completedId) {\r\n return this.getSites(startDays, endDays, completedId).sort((a,b) => b.DaysValue - a.DaysValue)\r\n }\r\n\r\n getSites(startDays, endDays, completedId) {\r\n return this.cardData.sites.filter((site) =>\r\n ((site.DaysValue >= startDays) &&\r\n (site.DaysValue < endDays) &&\r\n (site.CompletedId === completedId))\r\n );\r\n }\r\n\r\n getSiteTable(sites, title, cssClass) {\r\n\r\n const siteCount = sites.length;\r\n const siteString = (siteCount === 1) ? 'Site' : 'Sites'\r\n\r\n const tableRows = sites.map((site) => {\r\n return `\r\n \r\n ${site.SiteName} (${site.SiteIdDisplay}) | \r\n ${site.Days} days | \r\n
\r\n `;\r\n }).join('');\r\n\r\n return `\r\n \r\n \r\n `;\r\n }\r\n\r\n getPopoverOptions(data, title): PopoverOption {\r\n return {\r\n html: true,\r\n title: title,\r\n content: `\r\n \r\n
\r\n \r\n Min: | \r\n ${data.Min} | \r\n
\r\n \r\n Q1: | \r\n ${data.Q1} | \r\n
\r\n \r\n Mean: | \r\n ${data.Mean} | \r\n
\r\n \r\n Median: | \r\n ${data.Median} | \r\n
\r\n \r\n Q3: | \r\n ${data.Q3} | \r\n
\r\n \r\n Max: | \r\n ${data.Max} | \r\n
\r\n
\r\n
`,\r\n delay: { \"show\": 500, \"hide\": 100 },\r\n sanitize: false,\r\n trigger: 'hover',\r\n placement: 'top'\r\n }\r\n }\r\n\r\n updatedDetailsTabs() {\r\n const activeTab = (this.detailsTabState) ? this.inprogressTab : this.completedTab;\r\n const activeTabContent = (this.detailsTabState) ? this.inprogressTabContent : this.completedTabContent;\r\n const inactiveTab = (this.detailsTabState) ? this.completedTab : this.inprogressTab;\r\n const inactiveTabContent = (this.detailsTabState) ? this.completedTabContent : this.inprogressTabContent;\r\n\r\n activeTab.classList.add('active');\r\n inactiveTab.classList.remove('active');\r\n activeTabContent.classList.remove('d-none');\r\n inactiveTabContent.classList.add('d-none');\r\n }\r\n\r\n\r\n}\r\n\r\n","import { TaskTrackerGanttChart } from \"./gantt-chart\";\r\nimport { TaskTrackerDataTable } from \"./data-table\";\r\nimport { FormatDate } from \"../../../shared/scripts/date-functions\";\r\nimport { Task } from \"./task-tracker.entities\";\r\nimport { plainToClass } from \"class-transformer\";\r\n\r\n\r\nexport class TaskTrackerPage {\r\n\r\n tasks: Task[];\r\n ganttChart: TaskTrackerGanttChart;\r\n dataTable: TaskTrackerDataTable;\r\n\r\n constructor(title: string, taskJson: any, lastUpdate: string, showGanttTab: boolean, showMilestoneTaskFilter: boolean, showCriticalTaskFilter: boolean, showStudyFilter: boolean) {\r\n\r\n $(() => {\r\n //console.log(\"Task Tracker\", title, lastUpdate, taskJson);\r\n //console.log(showMilestoneTaskFilter, showCriticalTaskFilter, showStudyFilter);\r\n //console.log(Boolean(showMilestoneTaskFilter), Boolean(showStudyFilter));\r\n\r\n $('#page-title').text(title);\r\n $('#last-update-date').text(lastUpdate);\r\n\r\n this.tasks = plainToClass(Task, taskJson);\r\n this.tasks.forEach((t: Task) => {\r\n t.Initialize();\r\n })\r\n\r\n //console.log(this.tasks);\r\n\r\n if (showGanttTab) {\r\n this.ganttChart = new TaskTrackerGanttChart();\r\n this.ganttChart.initialize(this.tasks, showStudyFilter);\r\n }\r\n\r\n this.dataTable = new TaskTrackerDataTable(this.tasks, showMilestoneTaskFilter, showCriticalTaskFilter, showStudyFilter);\r\n\r\n this.setupTabWatcher();\r\n });\r\n }\r\n\r\n setupTabWatcher(): void {\r\n $('a[data-toggle=\"pill\"]').on('shown.bs.tab', (e) => {\r\n if (e.currentTarget.id == \"content-datatable-tab\") {\r\n this.dataTable.refresh();\r\n }\r\n else if (e.currentTarget.id == \"content-gantt-tab\") {\r\n //this.map.refresh();\r\n //console.log(\"gantt tab\");\r\n }\r\n });\r\n }\r\n\r\n}","\r\n\r\n\r\n\r\nexport class TableColumnVisibilityController {\r\n\r\n tableId: string;\r\n dropdownDivId: string;\r\n dropdownId: string;\r\n columnGroups: TableColumnGroup[];\r\n\r\n constructor(tableId: string, dropdownDivId: string, columnGroups: TableColumnGroup[]) {\r\n this.tableId = tableId;\r\n this.dropdownDivId = dropdownDivId;\r\n this.columnGroups = columnGroups;\r\n\r\n this.dropdownId = tableId + \"-column-filter-dropdown\";\r\n\r\n $('#' + dropdownDivId).empty().html(\"\");\r\n\r\n $('#' + this.dropdownId).selectpicker({\r\n dropupAuto: false,\r\n selectedTextFormat: \"count\",\r\n\r\n countSelectedText: (numSelected, numTotal) => {\r\n if (numSelected == numTotal) {\r\n return \"All Available\";\r\n }\r\n else {\r\n return numSelected + \" Selected\";\r\n }\r\n }\r\n });\r\n\r\n this.columnGroups.forEach((group: TableColumnGroup) => {\r\n $('#' + this.dropdownId).append(new Option(group.name, group.name, false, group.visible));\r\n });\r\n $('#' + this.dropdownId).selectpicker(\"refresh\");\r\n\r\n $('#' + this.dropdownId).on('changed.bs.select', (e, clickedIndex, isSelected, previousValue) => {\r\n this.updateColumnVisibility();\r\n });\r\n this.updateColumnVisibility();\r\n }\r\n\r\n refresh(): void {\r\n $('#' + this.dropdownId).selectpicker(\"refresh\");\r\n }\r\n\r\n updateColumnVisibility(): void {\r\n\r\n let values: string[] = ($('#' + this.dropdownId).val());\r\n\r\n let visibleColumns: number[] = [];\r\n let visibleStyles: string[] = [];\r\n let hiddenColumns: number[] = [];\r\n let hiddenStyles: string[] = [];\r\n this.columnGroups.forEach((colgroup: TableColumnGroup) => {\r\n if (values.includes(colgroup.name)) {\r\n visibleColumns.push(...colgroup.columns);\r\n if (colgroup.columnStyle.length > 0) { visibleStyles.push(colgroup.columnStyle); }\r\n }\r\n else {\r\n hiddenColumns.push(...colgroup.columns);\r\n if (colgroup.columnStyle.length > 0) { hiddenStyles.push(colgroup.columnStyle); }\r\n }\r\n });\r\n\r\n var table = $('#' + this.tableId).DataTable();\r\n if (hiddenColumns.length > 0) { table.columns(hiddenColumns).visible(false, false); }\r\n if (visibleColumns.length > 0) { table.columns(visibleColumns).visible(true, false); }\r\n\r\n hiddenStyles.forEach((style: string) => {\r\n table.columns('.' + style).visible(false, false);\r\n });\r\n visibleStyles.forEach((style: string) => {\r\n table.columns('.' + style).visible(true, false);\r\n });\r\n\r\n table.columns.adjust().draw(false); // adjust column sizing and redraw\r\n\r\n }\r\n}\r\n\r\n\r\nexport class TableColumnGroup {\r\n name: string;\r\n visible: boolean;\r\n columns: number[];\r\n columnStyle: string;\r\n\r\n constructor(name: string, visible: boolean, columns: number[], columnStyle: string) {\r\n this.name = name;\r\n this.visible = visible;\r\n this.columns = columns;\r\n this.columnStyle = columnStyle;\r\n }\r\n}","import { plainToClass } from \"class-transformer\";\r\nimport { GetDateFromString } from \"../../../../shared/scripts/date-functions\";\r\nimport { PadNumber } from \"../../../../shared/scripts/number-functions\";\r\n\r\n\r\nexport class SiteInfo {\r\n StudyId: number;\r\n AppendixIds: number[];\r\n SiteNumber: number;\r\n SiteName: string;\r\n PI: string = \"\";\r\n Randomized: number;\r\n ActivationDate: string;\r\n ActivationTimestamp: number;\r\n\r\n getSiteNameNumber(): string {\r\n return this.SiteName + \" (\" + PadNumber(this.SiteNumber + \"\", 3) + \")\";\r\n }\r\n\r\n getSiteNumberFormatted(): string {\r\n return PadNumber(this.SiteNumber + \"\", 3);\r\n }\r\n\r\n setActivationTimestamp(dayOfMonth: string = \"first\"): void {\r\n if (this.ActivationDate != undefined) {\r\n let date: Date = GetDateFromString(this.ActivationDate, \"slash-month-day-year\");\r\n\r\n if (dayOfMonth == \"first\") { date.setDate(1); }\r\n\r\n this.ActivationTimestamp = date.getTime();\r\n }\r\n }\r\n\r\n static Parse(infoJson: any): SiteInfo[] {\r\n return plainToClass(SiteInfo, infoJson);\r\n }\r\n\r\n static CreateNameMap(infoJson: any = undefined, info: SiteInfo[] = undefined): Map {\r\n\r\n if (infoJson != undefined) {\r\n info = plainToClass(SiteInfo, infoJson);\r\n }\r\n let dictionary: Map = new Map();\r\n info.map((entry: SiteInfo) => {\r\n dictionary.set(entry.SiteNumber, entry.SiteName);\r\n //dictionary.set(entry.SiteNumber, entry.getSiteNameNumber());\r\n });\r\n return dictionary;\r\n } \r\n\r\n static FormatSiteNumber(siteNumber: number): string {\r\n return PadNumber(siteNumber + \"\", 3);\r\n }\r\n}\r\n","// extracted by mini-css-extract-plugin","import { Appendix } from \"../../../../shared/entities/scripts/appendix\";\r\nimport { Site } from \"../../../../shared/entities/scripts/site\";\r\nimport { Study } from \"../../../../shared/entities/scripts/study\";\r\nimport { DoArraysOverlap } from \"../../../../shared/scripts/array-functions\";\r\nimport { FilterOption } from \"../../../../shared/scripts/filtering/filtering.entities\";\r\nimport { ResetDropdownFilters } from \"../../../../shared/scripts/filtering/filtering.utilities\";\r\nimport { Outcome } from \"../components/outcomes-tab/scripts/outcomes.entities\";\r\nimport { Visit } from \"../components/visits-tab/scripts/visits.entities\";\r\n\r\nexport interface IFilteringParent {\r\n FilterUpdated();\r\n}\r\n\r\nexport class FilterSection {\r\n\r\n parent: IFilteringParent;\r\n\r\n studies: Study[] = [];\r\n appendices: Appendix[] = [];\r\n sites: Site[] = [];\r\n visits: Visit[] = [];\r\n visitRequired: FilterOption[] = [];\r\n outcomes: Outcome[] = [];\r\n\r\n allStudyIds: number[] = []; activeStudyIds: number[] = []; selectedStudyIds: number[] = [];\r\n allAppendixIds: number[] = []; activeAppendixIds: number[] = []; selectedAppendixIds: number[] = [];\r\n allSiteIds: number[] = []; activeSiteIds: number[] = []; selectedSiteIds: number[] = [];\r\n allVisitIds: number[] = []; activeVisitIds: number[] = []; selectedVisitIds: number[] = [];\r\n allVisitRequiredIds: number[] = []; activeVisitRequiredIds: number[] = []; selectedVisitRequiredIds: number[] = [];\r\n allOutcomeIds: number[] = []; activeOutcomeIds: number[] = []; selectedOutcomeIds: number[] = [];\r\n\r\n studySelectId: string = \"#study-dropdown\";\r\n appendixSelectId: string = \"#appendix-dropdown\";\r\n siteSelectId: string = \"#site-dropdown\";\r\n visitSelectId: string = \"#visit-dropdown\";\r\n visitRequiredSelectId: string = \"#visit-required-dropdown\";\r\n outcomeSelectId: string = \"#outcome-dropdown\";\r\n\r\n constructor(studies: Study[], appendices: Appendix[], sites: Site[], visits: Visit[], visitRequired: FilterOption[], outcomes: Outcome[], parent: IFilteringParent) {\r\n\r\n this.parent = parent;\r\n\r\n this.studies = studies;\r\n this.appendices = appendices;\r\n this.sites = sites;\r\n this.visits = visits;\r\n this.visitRequired = visitRequired;\r\n this.outcomes = outcomes;\r\n\r\n this.allStudyIds = this.studies.map((s: Study) => s.Id);\r\n this.allAppendixIds = this.appendices.map((a: Appendix) => a.Id);\r\n this.allSiteIds = this.sites.map((s: Site) => s.Id);\r\n this.allVisitIds = this.visits.map((v: Visit) => v.Id);\r\n this.allVisitRequiredIds = this.visitRequired.map((v: FilterOption) => v.Id);\r\n this.allOutcomeIds = this.outcomes.map((o: Outcome) => o.Id);\r\n\r\n this.initializeFilters();\r\n this.resetFilters();\r\n }\r\n\r\n initializeFilters(): void {\r\n this.setupDropdown(\"study\", this.studySelectId, \"Platforms\");\r\n this.setupDropdown(\"appendix\", this.appendixSelectId, \"Trials\");\r\n this.setupDropdown(\"site\", this.siteSelectId, \"Sites\");\r\n this.setupDropdown(\"visit\", this.visitSelectId, \"\");\r\n this.setupDropdown(\"visit-required\", this.visitRequiredSelectId, \"\");\r\n this.setupDropdown(\"outcome\", this.outcomeSelectId, \"\");\r\n }\r\n\r\n setupDropdown(type: string, elementId: string, optionName: string): void {\r\n\r\n $(elementId).selectpicker({\r\n dropupAuto: false,\r\n selectedTextFormat: \"count\",\r\n\r\n countSelectedText: (numSelected, numTotal) => {\r\n\r\n let activeCount: number = 0;\r\n if (type == 'study') activeCount = this.activeStudyIds.length;\r\n else if (type == 'appendix') activeCount = this.activeAppendixIds.length;\r\n else if (type == 'site') activeCount = this.activeSiteIds.length;\r\n else if (type == 'visit') activeCount = this.activeVisitIds.length;\r\n else if (type == 'visit-required') activeCount = this.activeVisitRequiredIds.length;\r\n else if (type == 'outcome') activeCount = this.activeOutcomeIds.length;\r\n\r\n if (numSelected == numTotal) {\r\n return \"All \" + optionName + \" (n = \" + numSelected + \")\";\r\n }\r\n else {\r\n return numSelected + \" of \" + activeCount + \" \" + optionName + \" Selected\";\r\n }\r\n }\r\n });\r\n $(elementId).selectpicker(\"refresh\");\r\n\r\n $(elementId).on('changed.bs.select', (e, clickedIndex, isSelected, previousValue) => {\r\n let values: string[] = ($(elementId).val());\r\n this.filterDropdownOptions(type, values.map(i => Number(i)));\r\n $(elementId).selectpicker(\"refresh\");\r\n this.filterUpdated(this.selectedStudyIds, this.selectedAppendixIds, this.selectedSiteIds, this.selectedVisitIds, this.selectedVisitRequiredIds, this.selectedOutcomeIds);\r\n });\r\n }\r\n\r\n setDropdownOptions(type: string, elementId: string, selectedElementIds: number[], activeElementIds: number[], allElements: any[], optionParameterValue: string, optionParameterName: string): void {\r\n\r\n $(elementId).empty();\r\n\r\n allElements.forEach((element: any) => {\r\n let selected: boolean = false;\r\n let disabled: boolean = false;\r\n\r\n if (selectedElementIds.includes(element[optionParameterValue])) {\r\n selected = true;\r\n }\r\n\r\n if (!activeElementIds.includes(element[optionParameterValue])) {\r\n disabled = true;\r\n selected = false;\r\n }\r\n\r\n if (!disabled) {\r\n let option: any = new Option(element[optionParameterName], element[optionParameterValue], false, selected);\r\n //option.disabled = disabled;\r\n $(elementId).append(option);\r\n }\r\n });\r\n $(elementId).selectpicker(\"refresh\");\r\n }\r\n\r\n resetFilters(): void {\r\n this.selectedStudyIds = this.activeStudyIds = this.allStudyIds;\r\n this.selectedAppendixIds = this.activeAppendixIds = this.allAppendixIds;\r\n this.selectedSiteIds = this.activeSiteIds = this.allSiteIds;\r\n this.selectedVisitIds = this.activeVisitIds = this.allVisitIds;\r\n this.selectedVisitRequiredIds = this.activeVisitRequiredIds = this.allVisitRequiredIds;\r\n this.selectedOutcomeIds = this.activeOutcomeIds = this.allOutcomeIds;\r\n\r\n this.setDropdownOptions(\"study\", this.studySelectId, this.selectedStudyIds, this.allStudyIds, this.studies, \"Id\", \"Name\");\r\n this.filterDropdownOptions(\"study\", this.selectedStudyIds);\r\n\r\n ResetDropdownFilters([this.studySelectId, this.appendixSelectId, this.siteSelectId, this.visitSelectId, this.visitRequiredSelectId, this.outcomeSelectId]);\r\n\r\n this.filterUpdated(this.activeStudyIds, this.activeAppendixIds, this.activeSiteIds, this.activeVisitIds, this.activeVisitRequiredIds, this.activeOutcomeIds);\r\n }\r\n\r\n filterDropdownOptions(type: string, currentValues: number[]) {\r\n\r\n //console.log(type, currentValues);\r\n\r\n if (type == 'study') {\r\n this.selectedStudyIds = currentValues;\r\n this.activeAppendixIds = this.appendices.map((a: Appendix) => {\r\n if (this.selectedStudyIds.includes(a.StudyId)) return a.Id;\r\n }).filter((id: number) => id != undefined);\r\n this.activeSiteIds = this.sites.map((s: Site) => {\r\n if (DoArraysOverlap(this.selectedStudyIds, s.StudyIds)) return s.Id;\r\n }).filter((id: number) => id != undefined);\r\n\r\n //cohort selection activates all hubs/sites\r\n this.selectedAppendixIds = this.activeAppendixIds;\r\n this.selectedSiteIds = this.activeSiteIds;\r\n }\r\n else if (type == 'appendix') {\r\n this.selectedAppendixIds = currentValues;\r\n }\r\n else if (type == 'site') {\r\n //no filtering \"left\" to study or appendix based on selected sites\r\n this.selectedSiteIds = currentValues;\r\n }\r\n else if (type == \"visit\") {\r\n this.selectedVisitIds = currentValues;\r\n }\r\n else if (type == \"visit-required\") {\r\n this.selectedVisitRequiredIds = currentValues;\r\n }\r\n else if (type == \"outcome\") {\r\n this.selectedOutcomeIds = currentValues;\r\n }\r\n\r\n if (type != 'study') this.setDropdownOptions(\"study\", this.studySelectId, this.selectedStudyIds, this.allStudyIds, this.studies, \"Id\", \"Name\");\r\n if (type != 'appendix') this.setDropdownOptions(\"appendix\", this.appendixSelectId, this.activeAppendixIds, this.activeAppendixIds, this.appendices, \"Id\", \"Name\");\r\n if (type != 'site') this.setDropdownOptions(\"site\", this.siteSelectId, this.activeSiteIds, this.activeSiteIds, this.sites, \"Id\", \"Name\");\r\n if (type != 'visit') this.setDropdownOptions(\"visit\", this.visitSelectId, this.selectedVisitIds, this.activeVisitIds, this.visits, \"Id\", \"Name\");\r\n if (type != 'visit-required') this.setDropdownOptions(\"visit-required\", this.visitRequiredSelectId, this.selectedVisitRequiredIds, this.activeVisitRequiredIds, this.visitRequired, \"Id\", \"Name\");\r\n if (type != 'outcome') this.setDropdownOptions(\"outcome\", this.outcomeSelectId, this.selectedOutcomeIds, this.activeOutcomeIds, this.outcomes, \"Id\", \"Name\");\r\n }\r\n\r\n filterUpdated(studyIds: number[], appendixIds: number[], siteIds: number[], visitIds: number[], visitRequiredIds: number[], outcomeIds: number[]): void {\r\n\r\n //add \"0\" entry to signify all selected\r\n //if (this.selectedStudyIds.length == this.activeStudyIds.length) this.selectedStudyIds = [0].concat(studyIds);\r\n //if (this.selectedAppendixIds.length == this.activeAppendixIds.length) this.selectedAppendixIds = [0].concat(appendixIds);\r\n //if (this.selectedSiteIds.length == this.activeSiteIds.length) this.selectedSiteIds = [0].concat(siteIds);\r\n //if (this.selectedVisitIds.length == this.activeVisitIds.length) this.selectedVisitIds = [0].concat(visitIds);\r\n //this.parent.FilterUpdated(studyIds, appendixIds, siteIds, visitIds);\r\n this.parent.FilterUpdated();\r\n }\r\n\r\n refreshFilters(): void {\r\n this.filterUpdated(this.selectedStudyIds, this.selectedAppendixIds, this.selectedSiteIds, this.selectedVisitIds, this.selectedVisitRequiredIds, this.selectedOutcomeIds);\r\n }\r\n}","// extracted by mini-css-extract-plugin","// extracted by mini-css-extract-plugin","import { DetailsTableDialog } from \"../../../../../shared/components/dialogs/details-table-dialog/details-table-dialog.view\";\r\nimport { RenderDateMDY } from \"../../../../../shared/scripts/table-functions\";\r\nimport { Contract } from \"../../scripts/contracts.entities\";\r\n\r\nvar Highcharts = require('highcharts/highcharts');\r\n\r\nexport class ContractSummaryTab {\r\n\r\n dataTable: any;\r\n achievementChart: any;\r\n pieChart: any;\r\n\r\n dataRows: ContractSummaryTableRow[];\r\n detailsTableModal: DetailsTableDialog;\r\n\r\n //public static statusesOfInterest: string[] = [\"Requested\", \"Draft\", \"Negotiation\", \"Signature\", \"Fully Executed\", \"On Hold\", \"Withdrawn\"];\r\n public static statusesOfInterest: string[] = [\"Requested\", \"Draft\", \"Negotiation\", \"Signature\", \"Fully Executed\"];\r\n\r\n constructor() {\r\n\r\n this.initializeTable();\r\n this.initializeAchievementChart();\r\n this.initializePieChart();\r\n this.initiatlizeButtons();\r\n //this.updateContent(contracts);\r\n }\r\n\r\n updateContent(tableContracts: Contract[], chartContracts: Contract[]): void {\r\n\r\n this.dataTable.clear();\r\n this.dataTable.rows.add(this.getTableContent(tableContracts)).draw();\r\n\r\n this.setAchievementChartContent(chartContracts);\r\n }\r\n\r\n private getTableContent(contracts: Contract[]): ContractSummaryTableRow[] {\r\n\r\n let rowHash: Map = new Map();\r\n let overallRow: ContractSummaryTableRow = new ContractSummaryTableRow();\r\n\r\n overallRow.ContractType = \"Overall\";\r\n\r\n contracts = contracts.filter((contract: Contract) => ContractSummaryTab.statusesOfInterest.includes(contract.StatusGroup));\r\n contracts.forEach((contract: Contract) => {\r\n\r\n let row: ContractSummaryTableRow;\r\n if (rowHash.has(contract.ContractType)) { row = rowHash.get(contract.ContractType); }\r\n else { row = new ContractSummaryTableRow(); row.ContractType = contract.ContractType; }\r\n\r\n row.addValue(contract);\r\n overallRow.addValue(contract);\r\n\r\n rowHash.set(row.ContractType, row);\r\n });\r\n\r\n rowHash.set(\"Overall\", overallRow);\r\n\r\n this.dataRows = Array.from(rowHash.values());\r\n this.dataRows = this.dataRows.sort((a: ContractSummaryTableRow, b: ContractSummaryTableRow) => {\r\n if (a.ContractType == \"Overall\") return 1;\r\n else if (b.ContractType == \"Overall\") return -1;\r\n else return a.ContractType.localeCompare(b.ContractType);\r\n });\r\n this.dataRows.forEach((a: ContractSummaryTableRow, index: number) => {\r\n a.OrderBy = index\r\n a.setDisplayValues();\r\n });\r\n\r\n this.setPieChartContent(rowHash.get(\"Overall\"));\r\n\r\n return this.dataRows;\r\n }\r\n\r\n private setAchievementChartContent(contracts: Contract[]) {\r\n\r\n //console.log(contracts);\r\n\r\n //const start = new Date().getTime();\r\n contracts = contracts.filter((contract: Contract) => ContractSummaryTab.statusesOfInterest.includes(contract.StatusGroup));\r\n\r\n //get unique dates\r\n let uniqueDates: number[] = [];\r\n let dateTimes: number[] = [];\r\n contracts.forEach((contract: Contract) => {\r\n contract.HistoryUniqueDates.forEach((d: number) => {\r\n if (!dateTimes.includes(d)) {\r\n dateTimes.push(d);\r\n uniqueDates.push(d);\r\n }\r\n });\r\n });\r\n uniqueDates.sort((a: number, b: number) => a - b);\r\n\r\n //let chartDataLabels: string[] = ['Fully Executed', 'Signature', 'Negotiation', 'Draft', 'Requested'];\r\n //let chartSeriesColors: string[] = ['#0093af', '#5b9bd5', '#4c4c4c', '#f2ba49', '#de4846'];\r\n let chartDataLabels: string[] = ['Fully Executed', 'Signature', 'Negotiation'];\r\n let chartSeriesColors: string[] = ['#0093af', '#5b9bd5', '#4c4c4c'];\r\n let chartData: ContractSummaryChartDataSeries[] = [];\r\n let currentCounts: number[] = [ 0, 0, 0 ];\r\n\r\n chartDataLabels.map((label: string, index: number) => {\r\n chartData.push(new ContractSummaryChartDataSeries(label, chartSeriesColors[index]));\r\n });\r\n \r\n uniqueDates.forEach((date: number) => {\r\n currentCounts = [ 0, 0, 0 ];\r\n contracts.forEach((contract: Contract) => {\r\n\r\n let statusOnDay: string = contract.getContractStatusOnDate(date);\r\n\r\n for (let i: number = 0; i < chartDataLabels.length; ++i) {\r\n if (statusOnDay == chartDataLabels[i]) {\r\n currentCounts[i]++;\r\n }\r\n }\r\n });\r\n\r\n currentCounts.forEach((count: number, index: number) => {\r\n chartData[index].data.push( [date, count] );\r\n });\r\n });\r\n\r\n //const duration = new Date().getTime() - start;\r\n //console.log(uniqueDates);\r\n //console.log(chartData);\r\n //console.log(`contracts chart = ${duration / 1000}s`);\r\n\r\n //console.log(this.achievementChart);\r\n\r\n chartData.forEach((data: ContractSummaryChartDataSeries, index: number) => {\r\n this.achievementChart.series[index].setData(data.data);\r\n });\r\n $('#contract-count-label').html(contracts.length + \"\");\r\n }\r\n\r\n private setPieChartContent(overallCounts: ContractSummaryTableRow): void {\r\n let chartData: any[] = [];\r\n chartData.push({ name: \"Requested\", y: overallCounts.Values[0], color: '#de4846' });\r\n chartData.push({ name: \"Draft\", y: overallCounts.Values[1], color: '#f2ba49' });\r\n chartData.push({ name: \"Negotiation\", y: overallCounts.Values[2], color: '#673284' });\r\n chartData.push({ name: \"Signature\", y: overallCounts.Values[3], color: '#5b9bd5' });\r\n chartData.push({ name: \"Fully Executed\", y: overallCounts.Values[4], color: '#0093af' });\r\n\r\n //console.log(chartData);\r\n this.pieChart.series[0].setData(chartData);\r\n }\r\n\r\n private initializeTable(): void {\r\n this.dataTable = $('#contracts-summary-table').DataTable({\r\n //\"dom\": '<\"top-controls\"<\"column-select\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n searching: false,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n ordering: false,\r\n columns: [\r\n { data: \"ContractType\", title: \"Contract Type\", className: \"text-left font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.0\", sort: \"Values.0\" }, title: \"Requested\", className: \"text-center font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.1\", sort: \"Values.1\" }, title: \"Draft\", className: \"text-center font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.2\", sort: \"Values.2\" }, title: \"Negotiation\", className: \"text-center font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.3\", sort: \"Values.3\" }, title: \"Signature\", className: \"text-center font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.4\", sort: \"Values.4\" }, title: \"Fully Executed\", className: \"text-center font-size12 nowrap\" },\r\n { data: { _: \"DisplayValues.5\", sort: \"Values.5\" }, title: \"Overall\", className: \"overall-data text-center font-size12 nowrap\" },\r\n { data: \"OrderBy\", visible: false },\r\n ],\r\n columnDefs: [\r\n { target: 0, orderable: false }\r\n ],\r\n order: [[7, 'asc']],\r\n createdRow: (row, data, dataIndex) => {\r\n if (data[\"ContractType\"] == \"Overall\") { $(row).addClass(\"overall-data\"); }\r\n }\r\n });\r\n\r\n this.detailsTableModal = new DetailsTableDialog([\r\n { data: \"PrimeContract\", title: \"Prime Contract\", className: \"prime-contract text-center font-size12 nowrap\", width: '100px' },\r\n { data: { _: \"ContractNumber\", sort: \"ContractNumber\" }, title: \"Contract ID\", className: \"site-id text-center font-size12 nowrap\", width: '100px' },\r\n { data: { _: \"ContractNameDisplay\", sort: \"ContractName\" }, title: \"Contract Name\", className: \"text-left font-size12\" },\r\n { data: { _: \"StatusDisplay\", sort: \"Status\" }, title: \"Status\", className: \"text-left font-size12\" },\r\n { data: \"DateLastStatusStart\", title: \"Last Status Change\", className: \"completion-date text-left font-size12 nowrap\", render: RenderDateMDY },\r\n ], [[4, 'desc']]);\r\n }\r\n\r\n private initializeAchievementChart(): void {\r\n this.achievementChart = Highcharts.chart('contracts-summary-chart', {\r\n chart: {\r\n zoomType: 'x',\r\n resetZoomButton: {\r\n position: {\r\n align: 'right', // right by default\r\n verticalAlign: 'bottom',\r\n x: -50,\r\n y: 50\r\n },\r\n relativeTo: 'chart'\r\n }\r\n },\r\n tooltip: {\r\n //xDateFormat: '%m/%d/%Y',\r\n xDateFormat: '%d%b%Y',\r\n shared: true,\r\n split: false,\r\n enabled: true,\r\n formatter: function (tooltip) {\r\n //to make points appear in order in tooltip\r\n this.points.reverse();\r\n return tooltip.defaultFormatter.call(this, tooltip);\r\n },\r\n },\r\n title: {\r\n text: ''\r\n },\r\n credits: { enabled: false },\r\n plotOptions: {\r\n line: {\r\n marker: { enabled: false }\r\n }\r\n },\r\n yAxis: {\r\n title: { text: 'Contract Count' }\r\n },\r\n xAxis: {\r\n type: 'datetime',\r\n crosshair: {\r\n snap: true\r\n },\r\n labels: {\r\n formatter: function () {\r\n //return Highcharts.dateFormat('%m/%d/%y', this.value);\r\n return Highcharts.dateFormat('%d%b%y', this.value);\r\n }\r\n },\r\n events: {\r\n afterSetExtremes: function (event) {\r\n if (event.max < event.dataMax || event.min > event.dataMin) {\r\n $(\"#contracts-summary-chart-resetZoom\").show();\r\n\r\n }\r\n else {\r\n $(\"#contracts-summary-chart-resetZoom\").hide();\r\n }\r\n }\r\n }\r\n },\r\n legend: {\r\n enabled: true,\r\n reversed: true,\r\n margin: 30,\r\n align: 'right',\r\n verticalAlign: 'middle',\r\n layout: 'vertical'\r\n },\r\n series: [ //need empty to replace when data loaded\r\n { name: 'Fully Executed', data: [], color: '#0093af' },\r\n { name: 'Signature', data: [], color: '#5b9bd5' },\r\n { name: 'Negotiation', data: [], color: '#673284' },\r\n //{ name: 'Draft', data: [], color: '#f2ba49' },\r\n //{ name: 'Requested', data: [], color: '#de4846' },\r\n ] \r\n });\r\n }\r\n\r\n private initializePieChart(): void {\r\n this.pieChart = Highcharts.chart('contracts-summary-pie-chart', {\r\n chart: {\r\n //plotBackgroundColor: null,\r\n //plotBorderWidth: null,\r\n //plotShadow: false,\r\n type: 'pie',\r\n styledMode: false\r\n },\r\n title: {\r\n text: undefined\r\n },\r\n tooltip: {\r\n pointFormat: '{point.percentage:.1f}%'\r\n },\r\n credits: { enabled: false },\r\n //accessibility: {\r\n // point: {\r\n // valueSuffix: '%'\r\n // }\r\n //},\r\n plotOptions: {\r\n pie: {\r\n allowPointSelect: true,\r\n cursor: 'pointer',\r\n dataLabels: {\r\n enabled: false,\r\n format: '{point.percentage:.1f} %'\r\n },\r\n showInLegend: true\r\n }\r\n },\r\n series: [{\r\n colorByPoint: false,\r\n color: 'red',\r\n data: []\r\n }]\r\n });\r\n }\r\n\r\n showDetailTableModal(id: any, type: string): void {\r\n\r\n for (let i = 0; i < this.dataRows.length; ++i) {\r\n if (this.dataRows[i].ContractType == id) {\r\n let data: any[] = this.dataRows[i].getSelectedData(type);\r\n this.detailsTableModal.setData(data);\r\n this.detailsTableModal.show(this.dataRows[i].ContractType + \" (n = \" + data.length + \")\");\r\n break;\r\n }\r\n }\r\n }\r\n\r\n private initiatlizeButtons(): void {\r\n $(\"#contracts-summary-chart-resetZoom\").hide();\r\n\r\n $(\"#contracts-summary-chart-resetZoom\").click(() => {\r\n this.achievementChart.zoomOut();\r\n })\r\n }\r\n}\r\n\r\nexport class ContractSummaryTableRow {\r\n ContractType: string;\r\n OrderBy: number = -1;\r\n\r\n Values: number[] = [0,0,0,0,0,0];\r\n DisplayValues: string[] = [];\r\n Contracts: Contract[][];\r\n\r\n constructor() { this.Contracts = [[], [], [], [], []]; }\r\n\r\n addValue(contract: Contract): void {\r\n\r\n let index: number = -1;\r\n switch (contract.StatusGroup) {\r\n case \"Requested\": { index = 0; break; }\r\n case \"Draft\": { index = 1; break; }\r\n case \"Negotiation\": { index = 2; break; }\r\n case \"Signature\": { index = 3; break; }\r\n case \"Fully Executed\": { index = 4; break; }\r\n }\r\n\r\n if (index >= 0) {\r\n this.Values[index] += 1;\r\n this.Contracts[index].push(contract);\r\n\r\n this.Values[5] += 1; //overall\r\n }\r\n }\r\n\r\n setDisplayValues(): void {\r\n this.DisplayValues[0] = this.getDisplayValue(this.Values[0], \"requested\");\r\n this.DisplayValues[1] = this.getDisplayValue(this.Values[1], \"draft\");\r\n this.DisplayValues[2] = this.getDisplayValue(this.Values[2], \"negotiation\");\r\n this.DisplayValues[3] = this.getDisplayValue(this.Values[3], \"signature\");\r\n this.DisplayValues[4] = this.getDisplayValue(this.Values[4], \"fully-executed\");\r\n this.DisplayValues[5] = this.getDisplayValue(this.Values[5], \"overall\");\r\n }\r\n\r\n private getDisplayValue(count: number, type: string): string {\r\n return \"\" + count + \"\";\r\n }\r\n\r\n getSelectedData(type: string): any[] {\r\n if (type == \"requested\") return this.Contracts[0];\r\n else if (type == \"draft\") return this.Contracts[1];\r\n else if (type == \"negotiation\") return this.Contracts[2];\r\n else if (type == \"signature\") return this.Contracts[3];\r\n else if (type == \"fully-executed\") return this.Contracts[4];\r\n else if (type == \"overall\") return [...this.Contracts[0], ...this.Contracts[1], ...this.Contracts[2], ...this.Contracts[3], ...this.Contracts[4]];\r\n }\r\n}\r\n\r\nexport class ContractSummaryChartDataSeries {\r\n name: string;\r\n color: string;\r\n data: any[]; //Date.getTime(), count\r\n\r\n constructor(name: string, color: string) {\r\n this.name = name;\r\n this.color = color;\r\n this.data = [];\r\n }\r\n}","import { plainToClass } from \"class-transformer\";\r\nimport { DateUpdatedSection } from \"../../../../shared/components/date-updated-section/date-updated-section.view\";\r\nimport { RemoveElementFromArray } from \"../../../../shared/scripts/array-functions\";\r\nimport { RenderNullableNumber } from \"../../../../shared/scripts/table-functions\";\r\nimport { ExecutiveSummaryEntry } from \"./executive-summary.entities\";\r\n\r\n\r\n\r\nexport class ExecutiveSummaryPage {\r\n\r\n dataTables: any[] = [];\r\n\r\n openParentHashes: string[] = [];\r\n\r\n constructor(DataJson: any, LastUpdatedJson: any) {\r\n\r\n $(() => {\r\n\r\n new DateUpdatedSection(LastUpdatedJson);\r\n\r\n let data: ExecutiveSummaryEntry[] = plainToClass(ExecutiveSummaryEntry, DataJson);\r\n data.forEach((element: ExecutiveSummaryEntry) => element.initialize());\r\n\r\n let screenedRow: ExecutiveSummaryEntry = data.find(i => i.Label == \"Screened (Post Consent)\");\r\n let randomizedRow: ExecutiveSummaryEntry = data.find(i => i.Label == \"Randomized\");\r\n\r\n data.forEach((element: ExecutiveSummaryEntry) => {\r\n if (element.Type == 0 || element.Type == 2 || element.Type == 4) {\r\n if (element.Group == \"Participant-Screen\") element.createDisplayValues(screenedRow);\r\n else if (element.Group == \"Participant-Random\") element.createDisplayValues(randomizedRow);\r\n }\r\n });\r\n\r\n //console.log(data);\r\n\r\n ////console.log(allRows);\r\n\r\n this.initializeSection(data.filter((entry: ExecutiveSummaryEntry) => entry.Group == \"Site\"), \"executive-summary-sites-table\", \"Sites\");\r\n this.initializeSection(data.filter((entry: ExecutiveSummaryEntry) => entry.Group == \"Participant-Screen\" || entry.Group == \"Participant-Random\"), \"executive-summary-participants-table\", \"Participants\");\r\n //this.initializeSection(data.filter((entry: ExecutiveSummaryEntry) => entry.Group == \"AdverseEffects\"), \"executive-summary-adverse-effects-table\");\r\n\r\n //this.initializeTable(allRows);\r\n this.setRowVisibility();\r\n this.resetColumnVisibility();\r\n\r\n // Need to bind header clicks off body+selector rather than the table\r\n // because the fixedHeaders extension creates new tables when scrolling\r\n $('body').on('click', 'thead th', (event) => {\r\n\r\n this.dataTables.forEach((dt) => {\r\n if (dt.column($(event.currentTarget)).index()) {\r\n this.toggleColumns(dt.column($(event.currentTarget)).index(), dt);\r\n }\r\n });\r\n });\r\n });\r\n }\r\n\r\n private initializeSection(data: ExecutiveSummaryEntry[], tableId: string, tableTitle: string): void {\r\n\r\n //console.log(tableTitle, data);\r\n\r\n let parentRows: ExecutiveSummaryEntry[] = data.filter((element: ExecutiveSummaryEntry) => (element.Type != 2) && (element.Type != 5) && (element.Type != 6));\r\n parentRows.sort((a: ExecutiveSummaryEntry, b: ExecutiveSummaryEntry) => a.OrderBy - b.OrderBy);\r\n\r\n let alphabeticalChildrenRows: ExecutiveSummaryEntry[] = data.filter((entry: ExecutiveSummaryEntry) => entry.Type == 2 || entry.Type == 5); //need to sort child rows\r\n let sortedChildrenRows: ExecutiveSummaryEntry[] = data.filter((entry: ExecutiveSummaryEntry) => entry.Type == 6); //need to sort child rows\r\n alphabeticalChildrenRows.sort((a: ExecutiveSummaryEntry, b: ExecutiveSummaryEntry) => {\r\n // special sorting for reason child rows (Other and Unknown at the end)\r\n if (a.Type === 5) {\r\n if (a.Label.startsWith('Other')) return 1;\r\n if (b.Label.startsWith('Other')) return -1;\r\n if (a.Label.startsWith('Unknown')) return 1;\r\n if (b.Label.startsWith('Unknown')) return -1;\r\n }\r\n\r\n return a.Label.localeCompare(b.Label);\r\n });\r\n sortedChildrenRows.sort((a: ExecutiveSummaryEntry, b: ExecutiveSummaryEntry) => a.OrderBy - b.OrderBy);\r\n\r\n let childrenRows: ExecutiveSummaryEntry[] = [...alphabeticalChildrenRows, ...sortedChildrenRows];\r\n\r\n let allRows: ExecutiveSummaryEntry[] = [];\r\n for (let i = 0; i < parentRows.length; ++i) {\r\n\r\n parentRows[i].HasChildren = (parentRows[i].Type == 1) || (parentRows[i].Type == 4);\r\n parentRows[i].ParentHash = \"AlwaysShow\";\r\n allRows.push(parentRows[i]);\r\n\r\n if ((parentRows[i].Type == 1) || (parentRows[i].Type == 4)) {\r\n let children: ExecutiveSummaryEntry[] = childrenRows.filter((child: ExecutiveSummaryEntry) => child.ParentId == parentRows[i].Id);\r\n //console.log(children);\r\n for (let j = 0; j < children.length; ++j) {\r\n children[j].ParentHash = \"ChildOf\" + parentRows[i].Id;\r\n children[j].HasChildren = false;\r\n children[j].IsLastChild = (j == (children.length - 1));\r\n allRows.push(children[j]);\r\n\r\n if (!this.openParentHashes.includes(children[j].ParentHash)) { this.openParentHashes.push(children[j].ParentHash); }\r\n }\r\n }\r\n }\r\n\r\n this.initializeTable(allRows, tableId, tableTitle);\r\n }\r\n\r\n private initializeTable(data: ExecutiveSummaryEntry[], tableId: string, tableTitle: string): void {\r\n\r\n let dataTable: any = $('#' + tableId).DataTable({\r\n \"dom\": '<\"top-controls\"B0>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n search: false,\r\n scrollX: false,\r\n //scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n ordering: false,\r\n fixedHeader: true,\r\n columns: [\r\n { className: 'details-control', orderable: false, data: null, defaultContent: '', width: '5px' },\r\n { data: \"Label\", title: `${tableTitle}
`, className: \"text-left font-size12\", width: '400px' },\r\n { data: \"DisplayValues.0\", title: 'VITAL', className: \"text-center font-size12 nowrap protocol-column\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.1\", title: \"Autonomic
Dysfunction\", className: \"text-center font-size12 nowrap trial-column vital-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.2\", title: \"Cognitive
Dysfunction\", className: \"text-center font-size12 nowrap trial-column vital-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.3\", title: \"Exercise
Intolerance\", className: \"text-center font-size12 nowrap trial-column vital-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.4\", title: \"NEURO\", className: \"text-center font-size12 nowrap\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.5\", title: \"tDCS & BrainHQ\", className: \"text-center font-size12 nowrap trial-column neuro-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.6\", title: \"AUTONOMIC\", className: \"text-center font-size12 nowrap protocol-column\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.7\", title: \"IVIG\", className: \"text-center font-size12 nowrap trial-column autonomic-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.8\", title: \"Ivabradine\", className: \"text-center font-size12 nowrap trial-column autonomic-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.9\", title: \"SLEEP\", className: \"text-center font-size12 nowrap protocol-column\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.10\", title: \"Modafinil/Solriamfetol\", className: \"text-center font-size12 nowrap trial-column sleep-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.11\", title: \"Melatonin/Light\", className: \"text-center font-size12 nowrap trial-column sleep-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.12\", title: \"ENERGIZE\", className: \"text-center font-size12 nowrap protocol-column\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.13\", title: \"Cardiopulmonary Rehab\", className: \"text-center font-size12 nowrap trial-column energize-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.14\", title: \"Structured Pacing\", className: \"text-center font-size12 nowrap trial-column energize-trial\", width: '100px', render: RenderNullableNumber },\r\n { data: \"DisplayValues.15\", title: \"TOTAL 1\", className: \"text-center font-size12 nowrap\", width: '100px', render: RenderNullableNumber },\r\n { data: \"OrderBy\", title: \"\", visible: false },\r\n { data: \"ParentHash\", title: \"\", visible: false, searchable: true },\r\n ],\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n }\r\n ],\r\n order: [[5, 'asc']],\r\n data: data,\r\n rowCallback: (row, data: any, index) => {\r\n //hide expand for rows without children\r\n if (!data.HasChildren) {\r\n $('td:eq(0)', row).removeClass('details-control');\r\n\r\n if (data.Type == 2) {\r\n $(row).addClass('child-row');\r\n\r\n if (data.IsLastChild) {\r\n $(row).addClass('last');\r\n }\r\n }\r\n\r\n if (data.Type == 5 || data.Type == 6) {\r\n $(row).addClass('reason-child-row');\r\n\r\n if (data.IsLastChild) {\r\n $(row).addClass('last');\r\n }\r\n }\r\n }\r\n else {\r\n $(row).addClass('selectable');\r\n if (data.Type == 4) {\r\n $(row).addClass('reason-row');\r\n }\r\n }\r\n\r\n //add indent classes\r\n let cssIndent: string = \"indent-\" + data.Indent;\r\n $('td:eq(1)', row).addClass(cssIndent);\r\n\r\n if (data.Type == 3) {\r\n $('td:eq(1)', row).addClass('font-italic font-bold');\r\n }\r\n }\r\n });\r\n\r\n dataTable.on('click', 'tbody tr', (event) => {\r\n\r\n let selectedEntry: ExecutiveSummaryEntry = dataTable.row($(event.currentTarget)).data();\r\n let tr: any = $(event.currentTarget).closest('tr');\r\n // console.log(selectedEntry);\r\n\r\n if (selectedEntry.HasChildren) {\r\n let parentHash: string = \"ChildOf\" + selectedEntry.Id;\r\n\r\n if (this.openParentHashes.includes(parentHash)) {\r\n RemoveElementFromArray(this.openParentHashes, parentHash);\r\n tr.addClass('opened');\r\n }\r\n else {\r\n this.openParentHashes.push(parentHash);\r\n tr.removeClass('opened');\r\n }\r\n this.setRowVisibility();\r\n }\r\n });\r\n\r\n this.dataTables.push(dataTable);\r\n }\r\n\r\n private setRowVisibility(): any {\r\n let filterString: string = \"\";\r\n\r\n if (this.openParentHashes.length == 0) { filterString = \"ShowEverything\"; }\r\n else {\r\n this.openParentHashes.forEach((parentHash: string) => {\r\n if (filterString.length > 0) filterString += \"|\";\r\n filterString += parentHash;\r\n });\r\n }\r\n\r\n // console.log(\"filter string = \" + filterString);\r\n filterString = \"^((?!\" + filterString + \").)*$\";\r\n\r\n this.dataTables.forEach((dt: any) => { dt.columns(19).search(filterString, true, false).draw(); });\r\n }\r\n\r\n private resetColumnVisibility(clearAll=false) {\r\n\r\n this.dataTables.forEach((dt) => {\r\n // Associated Columns (Appendix/Trial, Symptom Cluster, etc.)\r\n [3,4,5,7,9,10,12,13,15,16].forEach((i) => dt.column(i).visible(false));\r\n // Protocols and Total\r\n [2,6,8,11,14,17].forEach((i) => {\r\n (clearAll) ? dt.column(i).visible(false) : dt.column(i).visible(true);\r\n $(dt.column(i).header()).removeClass('protocol-open');\r\n });\r\n\r\n });\r\n }\r\n\r\n private toggleColumns(idx: any, dataTable: any) {\r\n if ([2, 8, 11, 14].indexOf(idx) > -1) {\r\n const v = dataTable.column(idx + 1).visible();\r\n // this.resetColumnVisibility(!v); // Drill-down to appendices\r\n this.resetColumnVisibility(false); // Expand appendices inline\r\n if (!v) {\r\n this.dataTables.forEach((dt) => {\r\n dt.column(idx).visible(true);\r\n $(dt.column(idx).header()).addClass('protocol-open');\r\n dt.column(idx + 1).visible(true);\r\n if ([2, 8, 11, 14].indexOf(idx) > -1) dt.column(idx + 2).visible(true);\r\n if ([2].indexOf(idx) > -1) dt.column(idx + 3).visible(true);\r\n });\r\n }\r\n } else if (idx > 2) {\r\n this.resetColumnVisibility(false);\r\n }\r\n }\r\n\r\n}","\r\n\r\nexport class Site {\r\n Id: number;\r\n Name: string;\r\n StudyIds: number[] = [];\r\n ActivationTimestamps: number[] = [];\r\n\r\n constructor(id: number, name: string) { this.Id = id; this.Name = name; }\r\n}","import { plainToClass } from \"class-transformer\";\r\nimport { DateUpdatedSection } from \"../../../../shared/components/date-updated-section/date-updated-section.view\";\r\nimport { DoArraysOverlap, Unique } from \"../../../../shared/scripts/array-functions\";\r\nimport { IFilter } from \"../../../../shared/scripts/filtering/filtering.entities\";\r\nimport { GetSelectedDropdownValues, InitializeFilterDropdown } from \"../../../../shared/scripts/filtering/filtering.utilities\";\r\nimport { IStudyAppendixSiteFilterParent, StudyAppendixSiteFilterSection } from \"../../../../shared/scripts/filtering/filtering.study-appendix-site\";\r\nimport { CreateSeries_ActualDays, CreateSeries_EverDay } from \"../../../../shared/scripts/graphing/graphing-functions\";\r\nimport { ForecastGraphDataPoint } from \"./forecasts.entities\";\r\nimport { ForecastsLookup } from \"./forecasts.lookup\";\r\nimport { Study } from \"../../../../shared/entities/scripts/study\";\r\nimport { Appendix } from \"../../../../shared/entities/scripts/appendix\";\r\nimport { FormatNumberWithCommas } from \"../../../../shared/scripts/number-functions\";\r\n\r\nvar Highcharts = require('highcharts/highcharts');\r\nrequire('highcharts/modules/lollipop')(Highcharts);\r\nrequire('highcharts/highcharts-more')(Highcharts);\r\nrequire('highcharts/modules/dumbbell')(Highcharts);\r\nHighcharts.setOptions({\r\n lang: {\r\n thousandsSep: ','\r\n }\r\n});\r\n\r\nexport class ForecastsPage implements IStudyAppendixSiteFilterParent {\r\n\r\n filter: StudyAppendixSiteFilterSection;\r\n\r\n msaAspirational: ForecastGraphDataPoint[];\r\n msaActual: ForecastGraphDataPoint[];\r\n //prescreeningAspirational: ForecastGraphDataPoint[];\r\n //prescreeningActual: ForecastGraphDataPoint[];\r\n addendumAspirational: ForecastGraphDataPoint[];\r\n addendumActual: ForecastGraphDataPoint[];\r\n activationAspirational: ForecastGraphDataPoint[]\r\n activationActual: ForecastGraphDataPoint[];\r\n randomizedAspirational: ForecastGraphDataPoint[];\r\n randomizedActual: ForecastGraphDataPoint[];\r\n irbSubmissionsAspirational: ForecastGraphDataPoint[];\r\n irbSubmissionsActual: ForecastGraphDataPoint[];\r\n\r\n siteActivationChart: any;\r\n randomizationChart: any;\r\n addendumChart: any;\r\n //prescreeningChart: any;\r\n irbSubmissionsChart: any;\r\n msaChart: any;\r\n\r\n constructor(MsaAspirationalJson: any, MsaActualJson: any, IrbSubmissionsAspirationalJson: any, IrbSubmissionsActualJson: any, SiteAddendumAspirationalJson: any, SiteAddendumActualJson: any, ActivationAspirationalJson: any, ActivationActualJson: any, RandomizedAspirationalJson: any, RandomizedActualJson: any, LastUpdatedJson: any) {\r\n\r\n $(() => {\r\n\r\n new DateUpdatedSection(LastUpdatedJson);\r\n\r\n //console.log(\"MsaAspirational\", MsaAspirationalJson);console.log(\"MsaActual\", MsaActualJson); console.log(\"PrescreeningAspirational\", PrescreeningAspirationalJson); console.log(\"PrescreeningActual\", PrescreeningActualJson); console.log(\"SiteAddendumAspirational\", SiteAddendumAspirationalJson); console.log(\"SiteAddendumActual\", SiteAddendumActualJson); console.log(\"SiteActivationAspirational\", ActivationAspirationalJson); console.log(\"SiteActivationActual\", ActivationActualJson); console.log(\"RandomizedAspirational\", RandomizedAspirationalJson); console.log(\"RandomizedActual\", RandomizedActualJson);\r\n //console.log(ActivationActualJson);\r\n\r\n this.msaAspirational = this.initializeGraphDataPoints(MsaAspirationalJson);\r\n this.msaActual = this.initializeGraphDataPoints(MsaActualJson);\r\n //this.prescreeningAspirational = this.initializeGraphDataPoints(PrescreeningAspirationalJson);\r\n //this.prescreeningActual = this.initializeGraphDataPoints(PrescreeningActualJson);\r\n this.addendumAspirational = this.initializeGraphDataPoints(SiteAddendumAspirationalJson);\r\n this.addendumActual = this.initializeGraphDataPoints(SiteAddendumActualJson);\r\n this.activationAspirational = this.initializeGraphDataPoints(ActivationAspirationalJson);\r\n this.activationActual = this.initializeGraphDataPoints(ActivationActualJson);\r\n //this.randomizedAspirational = this.initializeGraphDataPoints(RandomizedAspirationalJson).filter((x: ForecastGraphDataPoint) => !x.IsStudyWide); //only study-wide shown in forecasting for now\r\n this.randomizedAspirational = this.initializeGraphDataPoints(RandomizedAspirationalJson); \r\n this.randomizedActual = this.initializeGraphDataPoints(RandomizedActualJson);\r\n this.irbSubmissionsAspirational = this.initializeGraphDataPoints(IrbSubmissionsAspirationalJson);\r\n this.irbSubmissionsActual = this.initializeGraphDataPoints(IrbSubmissionsActualJson);\r\n\r\n //console.log(\"MsaAspirational\", this.msaAspirational); console.log(\"MsaActual\", this.msaActual); console.log(\"SiteAddendumAspirational\", this.addendumAspirational); console.log(\"SiteAddendumActual\", this.addendumActual); console.log(\"SiteActivationAspirational\", this.activationAspirational); console.log(\"SiteActivationActual\", this.activationActual); console.log(\"RandomizedAspirational\", this.randomizedAspirational); console.log(\"RandomizedActual\", this.randomizedActual);\r\n //console.log(\"SiteActivationAspirational\", this.activationAspirational); console.log(\"SiteActivationActual\", this.activationActual);\r\n //console.log(\"SiteAddendumAspirational\", this.addendumAspirational);\r\n //console.log(\"SiteActivationAspirational\", this.activationAspirational);\r\n\r\n this.randomizationChart = this.initializeChart('randomization-forecast-chart', \"Participants\", this.createChartSeries('#152850', true));\r\n this.siteActivationChart = this.initializeChart('siteactivation-forecast-chart', \"Sites\", this.createChartSeries('#152850'));\r\n this.addendumChart = this.initializeChart('addendums-forecast-chart', \"Contracts\", this.createChartSeries('#152850'));\r\n //this.prescreeningChart = this.initializeChart('prescreening-forecast-chart', \"Contracts\", this.createChartSeries('#152850'));\r\n this.irbSubmissionsChart = this.initializeChart('irbsubmissions-forecast-chart', \"IRB Submissions\", this.createChartSeries('#152850'));\r\n this.msaChart = this.initializeChart('msa-forecast-chart', \"Contracts\", this.createChartSeries('#152850'));\r\n\r\n this.initializeFilter();\r\n\r\n $('#loading-indicator').hide();\r\n $('.loaded-content').removeClass('invisible');\r\n\r\n //this.triggerFilter();\r\n\r\n this.initiateZoomButtons();\r\n \r\n });\r\n\r\n }\r\n\r\n private initializeFilter(): void {\r\n let foundPrimeIds: number[] = [];\r\n let foundAppendixIds: number[] = [];\r\n let allPrimes: Study[] = [];\r\n let allAppendices: Appendix[] = [];\r\n\r\n //assumes site forecasts will be comprehensive. can always add a check for randomization forecast\r\n this.activationAspirational.forEach((x: ForecastGraphDataPoint) => {\r\n if (!foundPrimeIds.includes(x.PrimeId)) {\r\n allPrimes.push(new Study(x.PrimeId, Study.GetStudyName(x.PrimeId)));\r\n foundPrimeIds.push(x.PrimeId);\r\n }\r\n if (x.IsOverall == 0 && !foundAppendixIds.includes(x.AppendixId)) {\r\n allAppendices.push(new Appendix(x.AppendixId, x.PrimeId, Appendix.GetAppendixName(x.AppendixId)));\r\n foundAppendixIds.push(x.AppendixId);\r\n }\r\n });\r\n\r\n //console.log(allPrimes, allAppendices);\r\n\r\n this.filter = new StudyAppendixSiteFilterSection(\"#study-dropdown\", \"#appendix-dropdown\", undefined, allPrimes, allAppendices, undefined, this);\r\n\r\n //InitializeFilterDropdown('#prime-contract-dropdown', [\"AUTONOMIC\", \"ENERGIZE\", \"NEURO\", \"SLEEP\", \"VITAL\", \"Infrastructure\"], this);\r\n\r\n //static as not based on filters\r\n this.msaChart.series[0].setData(CreateSeries_ActualDays(this.msaAspirational), true);\r\n this.msaChart.series[1].setData(CreateSeries_EverDay(this.msaActual), true);\r\n $('#msa-count-label').html(this.setCountLabel(this.msaChart.series[1].dataMax));\r\n\r\n\r\n }\r\n\r\n private initializeGraphDataPoints(json: any): ForecastGraphDataPoint[] {\r\n\r\n let output: ForecastGraphDataPoint[] = plainToClass(ForecastGraphDataPoint, json);\r\n output.map((point: ForecastGraphDataPoint) => {\r\n point.initialize();\r\n return point;\r\n });\r\n //output = output.sort((a, b) => a.getTimestamp() - b.getTimestamp());\r\n return output;\r\n }\r\n\r\n private initializeChart(id: string, yAxisLabel: string, series: any[] ): any {\r\n let chart: any = Highcharts.chart(id, {\r\n chart: {\r\n zoomType: 'x',\r\n resetZoomButton: {\r\n position: {\r\n align: 'center', // right by default\r\n verticalAlign: 'bottom',\r\n x: 0,\r\n y: 0\r\n },\r\n relativeTo: 'chart'\r\n }\r\n },\r\n tooltip: {\r\n //xDateFormat: '%m/%d/%Y',\r\n xDateFormat: '%d%b%Y',\r\n shared: true,\r\n split: false,\r\n enabled: true,\r\n formatter: function (tooltip) {\r\n //to make points appear in order in tooltip\r\n this.points.reverse();\r\n return tooltip.defaultFormatter.call(this, tooltip);\r\n },\r\n },\r\n title: {\r\n text: ''\r\n },\r\n credits: { enabled: false },\r\n plotOptions: {\r\n line: {\r\n marker: { enabled: false }\r\n }\r\n },\r\n yAxis: {\r\n title: { text: yAxisLabel },\r\n labels: {\r\n formatter: function () {\r\n return Highcharts.numberFormat(this.value, 0, '', ',');\r\n }\r\n }\r\n },\r\n xAxis: {\r\n type: 'datetime',\r\n crosshair: {\r\n snap: true\r\n },\r\n labels: {\r\n formatter: function () {\r\n return Highcharts.dateFormat('%d%b%y', this.value);\r\n }\r\n },\r\n events: {\r\n afterSetExtremes: function (event) {\r\n let resetZoomId = \"#\" + id + \"-resetZoom\";\r\n if (event.max < event.dataMax || event.min > event.dataMin) {\r\n $(resetZoomId).show();\r\n \r\n }\r\n else {\r\n $(resetZoomId).hide();\r\n }\r\n }\r\n }\r\n },\r\n legend: {\r\n enabled: true,\r\n reversed: true,\r\n margin: 10,\r\n align: 'center',\r\n verticalAlign: 'bottom',\r\n layout: 'horizontal',\r\n symbolHeight: 8,\r\n symbolWidth: 8,\r\n symbolRadius: 4,\r\n symbolPadding: 7\r\n },\r\n series: series\r\n });\r\n\r\n return chart;\r\n }\r\n\r\n private createChartSeries(color: string, isRandomizationChart: boolean = false): any[] {\r\n let series: any[] = [];\r\n if (isRandomizationChart) {\r\n //series.push({ id: 'Expected w/o site payments', name: 'Expected (w/o + Site Payments)', data: [], color: '#fdc757', type: 'scatter', marker: { radius: 4 } });\r\n series.push({ id: 'Expected w/o site payments', name: 'Expected (w/o + Site Payments)', data: [], color: '#fdc757', type: 'line', lineWidth: 0, marker: { enabled: true, radius: 5, symbol: 'triangle' }, states: { hover: { enabled: false } } });\r\n }\r\n series.push({ id: 'Expected', name: 'Expected', data: [], color: '#de4846', dashStyle: 'dot', type: 'lollipop', connectorWidth: 1, marker: { radius: 3, symbol: 'circle' } });\r\n series.push({ id: 'Actual', name: 'Actual', data: [], color: color, dashStyle: 'solid', step: 'left', type: 'line', marker: { lineWidth: '0', lineColor: color } });\r\n\r\n return series;\r\n }\r\n\r\n FilterUpdated(studyIds: number[], appendixIds: number[], siteIds: number[]) {\r\n //console.log(\"FilterUpdated\", studyIds, appendixIds);\r\n\r\n //console.log(this.randomizedAspirational, this.randomizedActual);\r\n //console.log(this.activationAspirational, this.activationActual);\r\n //console.log(this.randomizedActual.filter((point: ForecastGraphDataPoint) => appendixIds.includes(point.AppendixId)));\r\n //console.log(CreateSeries_EverDay(this.randomizedActual.filter((point: ForecastGraphDataPoint) => appendixIds.includes(point.AppendixId))));\r\n //console.log(this.activationActual);\r\n\r\n this.randomizationChart.series[1].setData(CreateSeries_ActualDays(this.randomizedAspirational.filter((point: ForecastGraphDataPoint) => appendixIds.includes(point.AppendixId) && point.TypeId == 2 && point.IsOverall == 0)), true);\r\n this.randomizationChart.series[2].setData(CreateSeries_EverDay(this.randomizedActual.filter((point: ForecastGraphDataPoint) => appendixIds.includes(point.AppendixId))), true);\r\n\r\n //only show extra out-dated expected when only VITAL appendix is selected\r\n if ((studyIds.length == 1 && studyIds[0] == 2) || (appendixIds.length == 1 && appendixIds[0] == 1)) {\r\n this.randomizationChart.get('Expected w/o site payments').update({ visible: true, showInLegend: true });\r\n this.randomizationChart.series[0].setData(CreateSeries_ActualDays(this.randomizedAspirational.filter((point: ForecastGraphDataPoint) => appendixIds.includes(point.AppendixId) && point.TypeId == 4 && point.IsOverall == 0)), true); \r\n }\r\n else {\r\n this.randomizationChart.get('Expected w/o site payments').update({ visible: false, showInLegend: false });\r\n }\r\n $('#randomization-count-label').html(this.setCountLabel(this.randomizationChart.series[2].dataMax));\r\n\r\n\r\n //if all appendices for a study are selected, use overall, otherwise use appendix-level\r\n let appendicesToShow: number[] = Appendix.GetAppendicesToShow(appendixIds);\r\n this.siteActivationChart.series[0].setData(CreateSeries_ActualDays(this.activationAspirational.filter((point: ForecastGraphDataPoint) => appendicesToShow.includes(point.AppendixId))), true);\r\n this.siteActivationChart.series[1].setData(CreateSeries_EverDay(this.activationActual.filter((point: ForecastGraphDataPoint) => DoArraysOverlap(appendicesToShow, point.AppendixIds))), true);\r\n $('#activated-sites-count-label').html(this.setCountLabel(this.siteActivationChart.series[1].dataMax));\r\n\r\n let studiesToShow: number[] = Study.GetStudiesToShow(appendixIds);\r\n\r\n // this isn't optimal\r\n // bug with the forecast not being cleared on filter change.\r\n // on filter change, reusing the series (like other charts) led to a \"ghost\" set of lollipops not being cleared (the intitial set)\r\n this.addendumChart.series[1].remove();\r\n this.addendumChart.series[0].remove();\r\n let newSeries: any[] = this.createChartSeries('#152850');\r\n this.addendumChart.addSeries(newSeries[0]);\r\n this.addendumChart.addSeries(newSeries[1]);\r\n this.addendumChart.series[0].setData(CreateSeries_ActualDays(this.addendumAspirational.filter((point: ForecastGraphDataPoint) => studiesToShow.includes(point.PrimeId))), false);\r\n this.addendumChart.series[1].setData(CreateSeries_EverDay(this.addendumActual.filter((point: ForecastGraphDataPoint) => studiesToShow.includes(point.PrimeId))), false);\r\n this.addendumChart.redraw();\r\n $('#addendums-count-label').html(this.setCountLabel(this.addendumChart.series[1].dataMax));\r\n\r\n this.irbSubmissionsChart.series[0].setData(CreateSeries_ActualDays(this.irbSubmissionsAspirational.filter((point: ForecastGraphDataPoint) => studiesToShow.includes(point.PrimeId))), true);\r\n this.irbSubmissionsChart.series[1].setData(CreateSeries_EverDay(this.irbSubmissionsActual.filter((point: ForecastGraphDataPoint) => studiesToShow.includes(point.PrimeId))), true);\r\n $('#irbsubmissions-count-label').html(this.setCountLabel(this.irbSubmissionsChart.series[1].dataMax));\r\n\r\n }\r\n\r\n private setCountLabel(amount: number): string {\r\n if (amount == undefined) return \"0\";\r\n else return FormatNumberWithCommas(amount + \"\");\r\n }\r\n\r\n private initiateZoomButtons() {\r\n $(\"#randomization-forecast-chart-resetZoom\").hide();\r\n $(\"#siteactivation-forecast-chart-resetZoom\").hide();\r\n $(\"#addendums-forecast-chart-resetZoom\").hide();\r\n $(\"#irbsubmissions-forecast-chart-resetZoom\").hide();\r\n //$(\"#prescreening-forecast-chart-resetZoom\").hide();\r\n $(\"#msa-forecast-chart-resetZoom\").hide();\r\n\r\n $(\"#randomization-forecast-chart-resetZoom\").click(() => {\r\n this.randomizationChart.zoomOut();\r\n });\r\n\r\n $(\"#siteactivation-forecast-chart-resetZoom\").click(() => {\r\n this.siteActivationChart.zoomOut();\r\n });\r\n\r\n $(\"#addendums-forecast-chart-resetZoom\").click(() => {\r\n this.addendumChart.zoomOut();\r\n });\r\n\r\n //$(\"#prescreening-forecast-chart-resetZoom\").click(() => {\r\n // this.prescreeningChart.zoomOut();\r\n //})\r\n ;\r\n $(\"#irbsubmissions-forecast-chart-resetZoom\").click(() => {\r\n this.irbSubmissionsChart.zoomOut();\r\n });\r\n\r\n $(\"#msa-forecast-chart-resetZoom\").click(() => {\r\n this.msaChart.zoomOut();\r\n });\r\n }\r\n}\r\n","\r\n\r\nexport class FileTimestamp {\r\n Name: string;\r\n FileDate: string;\r\n\r\n initialize(): void { }\r\n}","import { FormatDate, GetDateFromString } from \"../../../../shared/scripts/date-functions\";\r\n\r\nexport class Announcement {\r\n Id: number;\r\n Title: string;\r\n Description: string;\r\n AnnouncementDate: string;\r\n AnnouncementDateFormatted: string;\r\n Link: string;\r\n ImageFile: string;\r\n SortOrder: number;\r\n\r\n Initialize(): void {\r\n const d = GetDateFromString(this.AnnouncementDate, 'hyphens-year-month-day');\r\n this.AnnouncementDateFormatted = FormatDate(d, \"mo-day-year\");\r\n }\r\n}","\r\n\r\n\r\nexport function SetActiveTab(tabGroupId: string, activeTabId: string): void {\r\n\r\n //console.log(\"SetActiveTab\", tabGroupId, activeTabId);\r\n\r\n $('#' + tabGroupId + ' .tab-pane').removeClass('show active');\r\n $('#' + activeTabId).addClass('show active');\r\n\r\n}","import { FormatNumberWithCommas } from \"../../../../shared/scripts/number-functions\";\r\nimport { TableColumnGroup, TableColumnVisibilityController } from \"../../../../shared/scripts/table-column-visibility\";\r\nimport { RenderDateMDY, RenderNullableNumberWithSeparators, RenderProgressBar } from \"../../../../shared/scripts/table-functions\";\r\nimport { FinancialEntry } from \"./financials.entities\";\r\nimport { Unique } from \"../../../../shared/scripts/array-functions\";\r\nimport { IFilter } from \"../../../../shared/scripts/filtering/filtering.entities\";\r\nimport { GetSelectedDate, GetSelectedDropdownValues, InitializeDateSelector, InitializeFilterDropdown } from \"../../../../shared/scripts/filtering/filtering.utilities\";\r\n\r\nexport class InvoicesTable implements IFilter {\r\n\r\n tableId: string = \"invoices-table\";\r\n tableWrapperId: string = this.tableId + \"_wrapper\";\r\n modalTableId: string = \"invoice-modal-table\";\r\n modalTableWrapperId: string = this.modalTableId + \"_wrapper\";\r\n dataTable: any;\r\n modalDataTable: any;\r\n tableColumnController: TableColumnVisibilityController;\r\n modalTableColumnController: TableColumnVisibilityController;\r\n allInvoices: FinancialEntry[];\r\n currentInvoices: FinancialEntry[];\r\n\r\n constructor(financials: FinancialEntry[]) {\r\n\r\n financials.forEach(financial => financial.initialize());\r\n this.allInvoices = financials;\r\n this.currentInvoices = this.allInvoices;\r\n this.initializeDataTable();\r\n\r\n $('a[data-toggle=\"tab\"]').on('shown.bs.tab', (e) => {\r\n //to setup dropdowns to go downwards\r\n\r\n if (e.currentTarget.id == \"invoices-tab\") {\r\n ['#study-dropdown', '#vendor-dropdown', '#invoice-status-dropdown'].forEach((id: string) => $(id).selectpicker(\"refresh\"));\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n initializeDataTable() {\r\n let columns: any[] = [\r\n { data: { _: \"StudyName\", display: \"StudyNameDisplay\" }, title: \"Category\", className: \"study text-left font-size12 nowrap\" },\r\n { data: { _: \"VendorName\", display: \"VendorNameDisplay\" }, title: \"Vendor\", className: \"vendor-name text-left font-size12 nowrap\" },\r\n { data: { _: \"Subcontract\", display: \"SubcontractDisplay\" }, title: \"Subcontract #\", className: \"subcontract text-left font-size12 nowrap\" },\r\n { data: { _: \"POID\", display: \"POIDDisplay\" }, title: \"PO/Vendor
ID\", className: \"poid text-center font-size12 nowrap\" },\r\n { data: { _: \"ProjectLevel\", display: \"ProjectLevelDisplay\" }, title: \"Project
Code\", className: \"project-level text-center font-size12 nowrap\" },\r\n //{ data: { _: \"SubcontractStartDateCompare\", display: \"SubcontractStartDateDisplay\" }, title: \"Subcontract Start Date\", className: \"subcontract-start-date text-left font-size12 nowrap\" },\r\n //{ data: { _: \"SubcontractEndDateParsed\", display: \"SubcontractEndDateDisplay\" }, title: \"Subcontract End Date\", className: \"subcontract-end-date text-left font-size12 nowrap\" },\r\n //{ data: \"SubcontractType\", title: \"Subcontract Type\", className: \"subcontract-type text-left font-size12 nowrap\" },\r\n //{ data: { _: \"FundedCommitmentAmount\", display: \"FundedCommitmentAmountDisplay\" }, title: \"Total Unrestricted Funds\", className: \"funded-commitment-amount text-left font-size12 nowrap\" },\r\n //{ data: { _: \"RestrictedFunds\", display: \"RestrictedFundsDisplay\" }, title: \"Total Restricted Funds\", className: \"restricted-funds text-left font-size12 nowrap\" },\r\n //{ data: { _: \"SubcontractSubcontractAmount\", display: \"SubcontractSubcontractAmountDisplay\" }, title: \"Total Subcontract Amount\", className: \"subcontract-funded-amount text-left font-size12 nowrap\" },\r\n { data: { _: \"RemainingCommitmentAmount\", display: \"RemainingCommitmentAmountDisplay\" }, title: \"Remaining
Unrestricted
At-Date\", className: \"remaining-commitment-amount text-center font-size12 nowrap\" },\r\n { data: { _: \"InvoicedToDate\", display: \"InvoicedToDateDisplay\" }, title: \"Invoiced
To-Date\", className: \"invoiced-to-date text-center font-size12 nowrap\"},\r\n { data: \"InvoiceNumber\", title: \"Invoice #\", className: \"invoice-number text-left font-size12 nowrap\" },\r\n { data: { _: \"InvoiceDateCompare\", display: \"InvoiceDateDisplay\" }, title: \"Invoice
Date\", className: \"invoice-date text-center font-size12 nowrap\" },\r\n { data: { _: \"InvoicedAmount\", display: \"InvoicedAmountDisplay\" }, title: \"Invoiced
Amount\", className: \"invoiced-amount text-center font-size12 nowrap\" },\r\n { data: \"InvoiceStatus\", title: \"Invoice
Status\", className: \"invoice-status text-center font-size12 nowrap\" },\r\n //{ data: \"EntryType\", title: \"Record
Type\", className: \"entry-type text-center font-size12 nowrap\" },\r\n ];\r\n\r\n let tableColumnGroups: TableColumnGroup[] = [\r\n //new TableColumnGroup(\"Project\", true, [], \"project\"),\r\n\r\n //new TableColumnGroup(\"Category\", true, [], \"study\"),\r\n new TableColumnGroup(\"Vendor\", true, [], \"vendor-name\"),\r\n new TableColumnGroup(\"Subcontract #\", false, [], \"subcontract\"),\r\n new TableColumnGroup(\"PO/Vendor ID\", false, [], \"poid\"),\r\n new TableColumnGroup(\"Project Code\", false, [], \"project-level\"),\r\n //new TableColumnGroup(\"Subcontract Start Date\", false, [], \"subcontract-start-date\"),\r\n //new TableColumnGroup(\"Subcontract End Date\", true, [], \"subcontract-end-date\"),\r\n //new TableColumnGroup(\"Subcontract Type\", false, [], \"subcontract-type\"),\r\n //new TableColumnGroup(\"Total Unrestricted Funds\", false, [], \"funded-commitment-amount\"),\r\n //new TableColumnGroup(\"Total Restricted Funds\", false, [], \"restricted-funds\"),\r\n //new TableColumnGroup(\"Total Subcontract Amount\", false, [], \"subcontract-funded-amount\"),\r\n new TableColumnGroup(\"Remaining Unrestricted At-Date\", false, [], \"remaining-commitment-amount\"),\r\n new TableColumnGroup(\"Invoiced To-Date\", false, [], \"invoiced-to-date\"),\r\n new TableColumnGroup(\"Invoice #\", true, [], \"invoice-number\"),\r\n new TableColumnGroup(\"Invoice Date\", true, [], \"invoice-date\"),\r\n new TableColumnGroup(\"Invoiced Amount\", true, [], \"invoiced-amount\"),\r\n new TableColumnGroup(\"Invoice Status\", true, [], \"invoice-status\"),\r\n //new TableColumnGroup(\"Record Type\", false, [], \"entry-type\"),\r\n ];\r\n\r\n this.dataTable = $('#' + this.tableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\"><\"added-since\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: true,\r\n pagingType: 'simple',\r\n pageLength: 50,\r\n search: true,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 1\r\n },\r\n columns: columns,\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n title: \"RECOVER-CT Invoices Export\"\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n }\r\n ],\r\n order: [\r\n [0, 'asc'],\r\n [1, 'asc'],\r\n [8, 'desc']\r\n ],\r\n data: this.allInvoices\r\n });\r\n\r\n $(\"#\" + this.tableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").empty().html('');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .spacer\").addClass('col-12 col-md-3');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").addClass('col-12 col-md-3 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('
');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.tableId, (this.tableId + '-column-select'), tableColumnGroups);\r\n\r\n $('#' + this.tableId).on('search.dt', (e, settings) => {\r\n this.setCountFoundLabel(this.dataTable.rows({ search: 'applied' }).count());\r\n });\r\n this.setCountFoundLabel(this.allInvoices.length);\r\n\r\n this.initializeDatePickers();\r\n this.initializeFilters(this.allInvoices);\r\n\r\n /*this.initializeModalLinks('#' + this.tableId);\r\n this.dataTable.on('draw', (e, settings) => {\r\n this.initializeModalLinks('#' + this.tableId);\r\n });*/\r\n\r\n $(\"#invoices-tab\").on('click', () => {\r\n setTimeout(() => {\r\n this.dataTable.columns.adjust().draw();\r\n }, 500)\r\n });\r\n \r\n }\r\n\r\n setCountFoundLabel(count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + this.tableId + '-found-count').text(\"No Entries Found\"); break;\r\n case 1: $('#' + this.tableId + '-found-count').text(\"1 Entry Found\"); break;\r\n default: $('#' + this.tableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Entries Found\");\r\n }\r\n }\r\n\r\n setModalCountFoundLabel(count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + this.modalTableId + '-found-count').text(\"No Entries Found\"); break;\r\n case 1: $('#' + this.modalTableId + '-found-count').text(\"1 Entry Found\"); break;\r\n default: $('#' + this.modalTableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Entries Found\");\r\n }\r\n }\r\n\r\n refresh(): void {\r\n this.dataTable.columns.adjust();\r\n this.tableColumnController.refresh();\r\n }\r\n\r\n updateContent(financials: FinancialEntry[]): void {\r\n this.dataTable.clear();\r\n this.dataTable.rows.add(financials).draw();\r\n\r\n this.setCountFoundLabel(financials.length);\r\n\r\n // reinitialize the links in table (old ones have been removed)\r\n //this.initializeModalLinks('#' + this.tableId);\r\n }\r\n\r\n clearSearchBar(): void {\r\n this.dataTable.search('').draw();\r\n }\r\n\r\n initializeDatePickers() {\r\n InitializeDateSelector(\"added-since-date\", this);\r\n InitializeDateSelector(\"added-before-date\", this);\r\n }\r\n\r\n initializeFilters(financials: FinancialEntry[]) {\r\n let studiesLists: string[] = [];\r\n this.allInvoices.map(invoice => {\r\n if (!studiesLists.includes(invoice.StudyName) && invoice.StudyName != null && invoice.StudyName != \"\") {\r\n studiesLists.push(invoice.StudyName);\r\n }\r\n })\r\n studiesLists.sort();\r\n let studiesAtEnd: string[] = [\"Infrastructure\", \"Other CT ACC Subcontracts\", \"Committee Members\"]\r\n studiesAtEnd.forEach(studyName => {\r\n let foundIndex = studiesLists.findIndex(study => study == studyName);\r\n if (foundIndex != -1) {\r\n studiesLists.splice(foundIndex, 1);\r\n studiesLists.push(studyName);\r\n }\r\n })\r\n\r\n InitializeFilterDropdown('#study-dropdown', studiesLists, this);\r\n InitializeFilterDropdown('#invoice-status-dropdown', Unique(this.allInvoices.map((invoice: FinancialEntry) => invoice.InvoiceStatus), true), this);\r\n InitializeFilterDropdown('#vendor-dropdown', Unique(this.allInvoices.map((invoice: FinancialEntry) => invoice.VendorName), true), this);\r\n //InitializeFilterDropdown('#entry-type-dropdown', Unique(this.allInvoices.map(invoice => invoice.EntryType), true), this);\r\n }\r\n\r\n triggerFilter(): void {\r\n //console.log(\"filter triggered\");\r\n let invoices = this.allInvoices;\r\n\r\n // Study\r\n invoices = invoices.filter(invoice => GetSelectedDropdownValues(\"#study-dropdown\").includes(invoice.StudyName)); \r\n // Project\r\n invoices = invoices.filter(invoice => GetSelectedDropdownValues(\"#invoice-status-dropdown\").includes(invoice.InvoiceStatus));\r\n // Status\r\n invoices = invoices.filter(invoice => GetSelectedDropdownValues(\"#vendor-dropdown\").includes(invoice.VendorName));\r\n // Entry Type\r\n //invoices = invoices.filter(invoice => GetSelectedDropdownValues(\"#entry-type-dropdown\").includes(invoice.EntryType));\r\n // Date\r\n if (GetSelectedDate(\"#added-since-date\") != null) {\r\n let addedSinceDateString: string = GetSelectedDate(\"#added-since-date\");\r\n let addedSinceDate: Date = new Date(addedSinceDateString);\r\n\r\n invoices = invoices.filter(invoice => {\r\n if (invoice.InvoiceDateCompare == null) {\r\n return false;\r\n }\r\n if (invoice.InvoiceDateCompare >= addedSinceDate) {\r\n return true;\r\n }\r\n else {\r\n return false;\r\n }\r\n });\r\n }\r\n if (GetSelectedDate(\"#added-before-date\") != null) {\r\n let addedSinceDateString: string = GetSelectedDate(\"#added-before-date\");\r\n let addedSinceDate: Date = new Date(addedSinceDateString);\r\n\r\n invoices = invoices.filter(invoice => {\r\n if (invoice.InvoiceDateCompare == null) {\r\n return false;\r\n }\r\n if (invoice.InvoiceDateCompare <= addedSinceDate) {\r\n return true;\r\n }\r\n else {\r\n return false;\r\n }\r\n });\r\n }\r\n this.currentInvoices = invoices;\r\n this.updateContent(invoices)\r\n }\r\n\r\n initializeModalLinks(tableId: string): void {\r\n let invoicesTable = this;\r\n // remove any existing click events (don't want to have multiple attached)\r\n $(tableId + \" .modal-link\").off('click');\r\n\r\n // add click event\r\n $(tableId + \" .modal-link\").on('click', function () {\r\n //console.log(\"Loading Content...\");\r\n // Show loading for modal\r\n $(\"#invoice-table-modal .loading\").removeClass(\"d-none\");\r\n $(\"#invoice-table-modal .loaded-content\").addClass(\"d-none\");\r\n \r\n let filterField = \"\";\r\n let filterText = $(this).text();\r\n let displayName = \"\";\r\n\r\n if ($(this).hasClass(\"study-modal\")) {\r\n filterField = \"StudyName\";\r\n displayName = \"Category Name\";\r\n }\r\n if ($(this).hasClass(\"project-name-modal\")) {\r\n filterField = \"ProjectName\";\r\n displayName = \"Project Name\";\r\n }\r\n if ($(this).hasClass(\"poid-modal\")) {\r\n filterField = \"POID\";\r\n displayName = \"PO/Vendor ID\"\r\n }\r\n if ($(this).hasClass(\"project-level-modal\")) {\r\n filterField = \"ProjectLevel\";\r\n displayName = \"Project Code / Charge Category\";\r\n }\r\n if ($(this).hasClass(\"project-number-modal\")) {\r\n filterField = \"ProjectNumber\";\r\n displayName = \"Project Number\";\r\n }\r\n if ($(this).hasClass(\"vendor-name-modal\")) {\r\n filterField = \"VendorName\";\r\n displayName = \"Vendor Name\"\r\n }\r\n if ($(this).hasClass(\"subcontract-modal\")) {\r\n filterField = \"Subcontract\";\r\n displayName = \"Subcontract\";\r\n }\r\n if ($(this).hasClass(\"subcontract-company-name-modal\")) {\r\n filterField = \"SubcontractCompanyName\";\r\n displayName = \"Subcontract Company Name\";\r\n }\r\n\r\n // Set Title\r\n $(\"#invoice-table-name\").text(`${displayName}: ${filterText}`);\r\n\r\n invoicesTable.loadModal(filterField, filterText);\r\n\r\n // Show Content\r\n setTimeout(() => {\r\n //console.log(\"Loaded!\");\r\n $(\"#invoice-table-modal .loading\").addClass(\"d-none\");\r\n $(\"#invoice-table-modal .loaded-content\").removeClass(\"d-none\");\r\n invoicesTable.modalDataTable.columns.adjust().draw();\r\n }, 1000)\r\n \r\n });\r\n }\r\n\r\n loadModal(fieldName: string, fieldText: string): void {\r\n // Filter Invoices based on selected field\r\n //let filteredInvoices = this.allInvoices.filter(invoice => invoice[fieldName] == fieldText); \r\n let filteredInvoices = this.currentInvoices.filter(invoice => invoice[fieldName] == fieldText); \r\n\r\n // Create Table\r\n this.loadModalTable(filteredInvoices);\r\n\r\n // Show Modal\r\n if (!$(\"#invoice-table-modal\").hasClass(\"show\")) {\r\n $(\"#invoice-table-modal\").modal(\"show\");\r\n }\r\n\r\n }\r\n\r\n loadModalTable(filteredInvoices: FinancialEntry[]): void {\r\n if (this.modalDataTable) {\r\n this.modalDataTable.destroy();\r\n $('#' + this.modalTableId).empty();\r\n } \r\n\r\n let modalColumns: any[] = [\r\n { data: { _: \"StudyName\", display: \"StudyNameDisplay\" }, title: \"Category\", className: \"modal-study text-left font-size12 nowrap\" },\r\n { data: { _: \"VendorName\", display: \"VendorNameDisplay\" }, title: \"Vendor\", className: \"modal-vendor-name text-left font-size12 nowrap\" },\r\n { data: { _: \"Subcontract\", display: \"SubcontractDisplay\" }, title: \"Subcontract #\", className: \"modal-subcontract text-left font-size12 nowrap\" },\r\n { data: { _: \"POID\", display: \"POIDDisplay\" }, title: \"PO/Vendor ID\", className: \"modal-poid text-left font-size12 nowrap\" },\r\n { data: { _: \"ProjectLevel\", display: \"ProjectLevelDisplay\" }, title: \"Project Code\", className: \"modal-project-level text-left font-size12 nowrap\" },\r\n { data: { _: \"SubcontractStartDateCompare\", display: \"SubcontractStartDateDisplay\" }, title: \"Subcontract Start Date\", className: \"modal-subcontract-start-date text-left font-size12 nowrap\" },\r\n { data: { _: \"SubcontractEndDateCompare\", display: \"SubcontractEndDateDisplay\" }, title: \"Subcontract End Date\", className: \"modal-subcontract-end-date text-left font-size12 nowrap\" },\r\n { data: \"SubcontractType\", title: \"Subcontract Type\", className: \"modal-subcontract-type text-left font-size12 nowrap\" },\r\n { data: { _: \"FundedCommitmentAmount\", display: \"FundedCommitmentAmountDisplay\" }, title: \"Total Unrestricted Funds\", className: \"modal-funded-commitment-amount text-left font-size12 nowrap\" },\r\n\r\n { data: { _: \"SubcontractSubcontractAmount\", display: \"SubcontractSubcontractAmountDisplay\" }, title: \"Total Subcontract Amount\", className: \"modal-subcontract-funded-amount text-left font-size12 nowrap\" },\r\n\r\n { data: { _: \"RestrictedFunds\", display: \"RestrictedFundsDisplay\" }, title: \"Total Restricted Funds\", className: \"modal-restricted-funds text-left font-size12 nowrap\" },\r\n { data: { _: \"RemainingCommitmentAmount\", display: \"RemainingCommitmentAmountDisplay\" }, title: \"Remaining Unrestricted Funds\", className: \"modal-remaining-commitment-amount text-left font-size12 nowrap\" },\r\n { data: \"InvoiceNumber\", title: \"Invoice #\", className: \"modal-invoice-number text-left font-size12 nowrap\" },\r\n { data: { _: \"InvoiceDateCompare\", display: \"InvoiceDateDisplay\" }, title: \"Invoice Date\", className: \"modal-invoice-date text-left font-size12 nowrap\" },\r\n { data: { _: \"InvoicedAmount\", display: \"InvoicedAmountDisplay\" }, title: \"Invoiced Amount\", className: \"modal-invoiced-amount text-left font-size12 nowrap\" },\r\n { data: \"InvoiceStatus\", title: \"Invoice Status\", className: \"modal-invoice-status text-left font-size12 nowrap\" },\r\n { data: \"EntryType\", title: \"Record Type\", className: \"modal-entry-type text-left font-size12 nowrap\" },\r\n //{ data: \"VendorNumber\", title: \"Vendor Number\", className: \"modal-vendor-info modal-vendor-number text-left font-size12 nowrap\" },\r\n //{ data: { _: \"ProjectName\", display: \"ProjectNameDisplay\" }, title: \"Project\", className: \"modal-project text-left font-size12 nowrap\" },\r\n //{ data: { _: \"ProjectNumber\", display: \"ProjectNumberDisplay\" }, title: \"Project Number\", className: \"modal-project-info modal-project-number text-left font-size12 nowrap\" },\r\n //{ data: { _: \"POEndDateCompare\", display: \"POEndDateDisplay\" }, title: \"PO End Date\", className: \"modal-po-end-date text-left font-size12 nowrap\" },\r\n //{ data: \"POReleaseNumber\", title: \"PO Release Number\", className: \"modal-project-info modal-po-release-number text-left font-size12 nowrap\" },\r\n //{ data: { _: \"SubcontractCompanyName\", display: \"SubcontractCompanyNameDisplay\" }, title: \"Subcontract Company Name\", className: \"modal-subcontract-info modal-subcontract-company-name text-left font-size12 nowrap\" },\r\n ]; \r\n\r\n this.modalDataTable = $('#' + this.modalTableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: true,\r\n pagingType: 'simple',\r\n pageLength: 25,\r\n search: true,\r\n scrollX: true,\r\n scrollY: '50vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 1\r\n },\r\n columns: modalColumns,\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n }\r\n ],\r\n order: [\r\n [0, 'asc'],\r\n [1, 'asc'],\r\n [13, 'desc']\r\n ],\r\n data: filteredInvoices,\r\n /*\"initComplete\": function (settings, json) {\r\n \r\n }*/\r\n });\r\n\r\n let selectedColumns: string[] = GetSelectedDropdownValues(\"#invoices-table-column-filter-dropdown\");\r\n\r\n let modalTableColumnGroups: TableColumnGroup[] = [\r\n new TableColumnGroup(\"Category\", selectedColumns.includes(\"Category\"), [], \"modal-study\"),\r\n new TableColumnGroup(\"Vendor\", selectedColumns.includes(\"Vendor\"), [], \"modal-vendor-name\"),\r\n new TableColumnGroup(\"Subcontract #\", selectedColumns.includes(\"Subcontract #\"), [], \"modal-subcontract\"),\r\n new TableColumnGroup(\"PO/Vendor ID\", selectedColumns.includes(\"ID\"), [], \"modal-poid\"),\r\n new TableColumnGroup(\"Project Code\", selectedColumns.includes(\"Project Code / Charge Category\"), [], \"modal-project-level\"),\r\n new TableColumnGroup(\"Subcontract Start Date\", selectedColumns.includes(\"Subcontract Start Date\"), [], \"modal-subcontract-start-date\"),\r\n new TableColumnGroup(\"Subcontract End Date\", selectedColumns.includes(\"Subcontract End Date\"), [], \"modal-subcontract-end-date\"),\r\n new TableColumnGroup(\"Subcontract Type\", selectedColumns.includes(\"Subcontract Type\"), [], \"modal-subcontract-type\"),\r\n new TableColumnGroup(\"Total Unrestricted Funds\", selectedColumns.includes(\"Total Unrestricted Funds\"), [], \"modal-funded-commitment-amount\"),\r\n new TableColumnGroup(\"Total Restricted Funds\", selectedColumns.includes(\"Total Restricted Funds\"), [], \"modal-restricted-funds\"),\r\n new TableColumnGroup(\"Total Subcontract Amount\", selectedColumns.includes(\"Total Subcontract Amount\"), [], \"modal-subcontract-funded-amount\"),\r\n new TableColumnGroup(\"Remaining Unrestricted Funds\", selectedColumns.includes(\"Remaining Unrestricted Funds\"), [], \"modal-remaining-commitment-amount\"),\r\n new TableColumnGroup(\"Invoice #\", selectedColumns.includes(\"Invoice #\"), [], \"modal-invoice-number\"),\r\n new TableColumnGroup(\"Invoice Date\", selectedColumns.includes(\"Invoice Date\"), [], \"modal-invoice-date\"),\r\n new TableColumnGroup(\"Invoiced Amount\", selectedColumns.includes(\"Invoiced Amount\"), [], \"modal-invoiced-amount\"),\r\n new TableColumnGroup(\"Invoice Status\", selectedColumns.includes(\"Invoice Status\"), [], \"modal-invoice-status\"),\r\n new TableColumnGroup(\"Record Type\", selectedColumns.includes(\"Record Type\"), [], \"modal-entry-type\"),\r\n ];\r\n\r\n $(\"#\" + this.modalTableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.modalTableWrapperId + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.modalTableWrapperId + \" .column-select\").empty().html('');\r\n\r\n $(\"#\" + this.modalTableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n $(\"#\" + this.modalTableWrapperId + \" .spacer\").addClass('col-12 col-md-3');\r\n $(\"#\" + this.modalTableWrapperId + \" .count-found\").addClass('col-12 col-md-3 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n $(\"#\" + this.modalTableWrapperId + \" .count-found\").prepend('
');\r\n $(\"#\" + this.modalTableWrapperId + \" .count-found\").prepend('');\r\n\r\n this.modalTableColumnController = new TableColumnVisibilityController(this.modalTableId, (this.modalTableId + '-column-select'), modalTableColumnGroups);\r\n\r\n $('#' + this.modalTableId).on('search.dt', (e, settings) => {\r\n this.setModalCountFoundLabel(this.modalDataTable.rows({ search: 'applied' }).count());\r\n });\r\n\r\n this.setModalCountFoundLabel(filteredInvoices.length);\r\n\r\n this.initializeModalLinks(\"#\" + this.modalTableId);\r\n this.modalDataTable.on('draw', () => {\r\n this.initializeModalLinks(\"#\" + this.modalTableId);\r\n }) \r\n \r\n }\r\n\r\n /*initializeModal(): void {\r\n // Runs after modal has finished opening\r\n $('#invoice-table-modal').on('shown.bs.modal', (e) => {\r\n // Readjust columns\r\n //this.modalDataTable.columns.adjust().draw();\r\n })\r\n }*/\r\n}","// extracted by mini-css-extract-plugin","\r\nfunction GetMonthAbbr(date: Date): string {\r\n return [\r\n \"Jan\",\r\n \"Feb\",\r\n \"Mar\",\r\n \"Apr\",\r\n \"May\",\r\n \"Jun\",\r\n \"Jul\",\r\n \"Aug\",\r\n \"Sep\",\r\n \"Oct\",\r\n \"Nov\",\r\n \"Dec\",\r\n ][date.getMonth()];\r\n}\r\n\r\n/*\r\nexport function FormatDate(date): string {\r\n\r\n if (date !== undefined && date !== \"\") {\r\n var myDate = new Date(date);\r\n var month = GetMonthAbbr(myDate);\r\n var str = myDate.getDate() + \" \" + month + \" \" + myDate.getFullYear();\r\n return str;\r\n }\r\n return \"\";\r\n}\r\n\r\nexport function FormatDateMonthAbbrDayYear(date): string {\r\n if (date !== undefined && date !== \"\") {\r\n var myDate = new Date(date);\r\n var month = GetMonthAbbr(myDate);\r\n return `${month} ${myDate.getDate()} ${myDate.getFullYear()}`;\r\n }\r\n return \"\";\r\n}\r\n*/\r\n\r\nexport function FormatDate(date: Date, format: string = \"day-mo-year\"): string {\r\n if (date == undefined) return \"\";\r\n let month: string = GetMonthAbbr(date);\r\n\r\n if (format == \"mo-day-year\") { //Jan 15 2024\r\n return month + \" \" + String(date.getDate()).padStart(2, '0') + \" \" + date.getFullYear();\r\n }\r\n else if (format == \"day-mo-year\") { //15 Jan 2024\r\n return String(date.getDate()).padStart(2, '0') + \" \" + month + \" \" + date.getFullYear();\r\n }\r\n}\r\n\r\nexport function GetDateFromString(dateString: string, format: string): Date {\r\n\r\n if (dateString == undefined || dateString.length == 0) return undefined;\r\n\r\n if (format == \"hyphens-year-month-day\") {\r\n let parts: string[] = dateString.split(\"-\");\r\n return new Date(Number.parseInt(parts[0]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[2]), 12, 0, 0);\r\n }\r\n else if (format == \"hyphens-year-month-day-time\") {\r\n let dateTime: string[] = dateString.split(\" \");\r\n let parts: string[] = dateTime[0].split(\"-\");\r\n return new Date(Number.parseInt(parts[0]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[2]), 12, 0, 0);\r\n }\r\n else if (format == \"hyphens-year-month-day-timezone\") {\r\n let trimmed = dateString.substr(0, dateString.indexOf(\"T\"));\r\n return this.GetDateFromString(trimmed, \"hyphens-year-month-day\");\r\n }\r\n else if (format == \"slash-month-day-year\") {\r\n let parts: string[] = dateString.split(\"/\");\r\n return new Date(Number.parseInt(parts[2]), Number.parseInt(parts[0]) - 1, Number.parseInt(parts[1]), 12, 0, 0);\r\n }\r\n else return null;\r\n}\r\n\r\nexport function SortDates(d1: Date, d2: Date): number {\r\n if (d1 == undefined) { d1 = new Date(new Date(8640000000000000)); }\r\n if (d2 == undefined) { d2 = new Date(new Date(8640000000000000)); }\r\n return d1.getTime() - d2.getTime();\r\n}\r\n\r\nexport function GetMonthFromNumber(monthNum: number, useShortName: boolean = false): string {\r\n // if number is outside month range\r\n if (monthNum > 12 || monthNum < 1) {\r\n return \"\";\r\n }\r\n\r\n const monthNames = [\"\", \"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\r\n \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"\r\n ];\r\n\r\n const shortMonthNames = [\"\", \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"June\",\r\n \"July\", \"Aug\", \"Sept\", \"Oct\", \"Nov\", \"Dec\"\r\n ];\r\n\r\n if (useShortName) {\r\n return shortMonthNames[monthNum];\r\n }\r\n\r\n return monthNames[monthNum];\r\n}","import { ComputePercent } from \"../../../../shared/scripts/number-functions\";\r\n\r\n\r\nexport class ExecutiveSummaryEntry {\r\n Id: number;\r\n Group: string;\r\n Label: string;\r\n Type: number; //0: main row (no children), 1: main row (with children), 2: child row, 3: header row\r\n OrderBy: number;\r\n ParentId: number;\r\n Counts: number[];\r\n Indent: number;\r\n\r\n ParentHash: string; //assumes a single nesting by GROUP\r\n HasChildren: boolean;\r\n IsLastChild: boolean;\r\n\r\n DisplayValues: string[] = [];\r\n\r\n StudyColumns = [0,4,6,9,12];\r\n TrialColumns = [1,2,3,5,7,8,10,11,13,14];\r\n\r\n initialize(): void {\r\n if (this.Type == 3) { this.Counts = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; }\r\n else {\r\n const total = this.StudyColumns.reduce((acc, cv) => acc + this.Counts[cv], 0);\r\n this.Counts[15] = total;\r\n }\r\n\r\n //if (this.Type == 1 || 2 || 5 || 6) {\r\n this.DisplayValues = this.Counts.map((count: number) => count + \"\");\r\n //}\r\n\r\n // Take out the total for reason rows\r\n if (this.Type == 5 || this.Type == 6) {\r\n this.DisplayValues[this.DisplayValues.length-1] = '-';\r\n }\r\n\r\n // Take out Appendix/Trial values on rows containing data for unassigned participants\r\n if (this.shouldSkipValue()) {\r\n this.TrialColumns.forEach((idx) => this.DisplayValues[idx] = '');\r\n }\r\n\r\n // Remove site values for VITAL symptom clusters\r\n if ([1,2].indexOf(this.Id) > -1) {\r\n [1,2,3].forEach((idx) => this.DisplayValues[idx] = '');\r\n }\r\n\r\n }\r\n\r\n createDisplayValues(parentEntry: ExecutiveSummaryEntry): void {\r\n\r\n this.DisplayValues = this.Counts.map((count: number, index: number) => {\r\n\r\n if ((this.shouldSkipValue()) && this.TrialColumns.indexOf(index) > -1) return \"\";\r\n else if (count == -1 || parentEntry.Counts[index] == -1) return \"\";\r\n else {\r\n let percent: number = ComputePercent(count, parentEntry.Counts[index]);\r\n if (percent >= 0) return count + \" (\" + percent.toFixed(0) + \"%)\";\r\n else return count + \"\";\r\n }\r\n });\r\n }\r\n\r\n shouldSkipValue() {\r\n return (([4,5,6].indexOf(this.Id) > -1) || (this.ParentId === 4));\r\n }\r\n\r\n\r\n}","\r\n\r\nexport class Appendix {\r\n Id: number;\r\n StudyId: number;\r\n Name: string;\r\n\r\n constructor(id: number, studyId: number, name: string) { this.Id = id; this.StudyId = studyId; this.Name = name; }\r\n\r\n static GetAppendixName(id: number): string {\r\n switch (id) {\r\n case (0): return \"VITAL (Overall)\";\r\n case (1): return \"VITAL-1 (Paxlovid)\";\r\n case (2): return \"NEURO (Overall)\";\r\n case (3): return \"NEURO-1 (tDCS & BrainHQ)\";\r\n case (4): return \"SLEEP (Overall)\";\r\n case (5): return \"SLEEP-1 (Modafinil/Solriamfetol)\";\r\n case (6): return \"SLEEP-2 (Melatonin/Light)\";\r\n case (7): return \"AUTONOMIC (Overall)\";\r\n case (8): return \"AUTONOMIC-1 (IVIG)\";\r\n case (9): return \"AUTONOMIC-2 (Ivabradine)\";\r\n case (10): return \"ENERGIZE (Overall)\";\r\n case (11): return \"ENERGIZE-1 (Cardiopulmonary Rehab)\";\r\n case (12): return \"ENERGIZE-2 (Structured Pacing)\";\r\n case (90): return \"VITAL-Unassigned\";\r\n case (91): return \"NEURO-Unassigned\";\r\n case (92): return \"AUTONOMIC-Unassigned\";\r\n case (93): return \"SLEEP-Unassigned\";\r\n case (94): return \"ENERGIZE-Unassigned\";\r\n default: return \"Unknown\";\r\n }\r\n }\r\n\r\n static GetOutcomeCount(appendixId: number): number {\r\n switch (appendixId) {\r\n case (1): return 3;\r\n case (3): return 1;\r\n default: return 0;\r\n }\r\n }\r\n\r\n //sorts in-place, with Unassigned at end of list\r\n static SortByName(list: Appendix[]): void {\r\n list.sort((a: Appendix, b: Appendix) => {\r\n if ((a.Id >= 90 && b.Id >= 90) || (a.Id < 90 && b.Id < 90)) { return a.Name.localeCompare(b.Name); }\r\n else if (a.Id >= 90 && b.Id < 90) { return 1; }\r\n else if (a.Id < 90 && b.Id >= 90) { return -1; }\r\n else return 0;\r\n });\r\n }\r\n\r\n static GetAppendicesToShow(appendixIds: number[]): number[] {\r\n let appendicesToShow: number[] = [];\r\n\r\n if (appendixIds.includes(1)) { appendicesToShow.push(1); } //VITAL-1\r\n if (appendixIds.includes(3)) { appendicesToShow.push(3); } //NEURO-1\r\n\r\n //SLEEP\r\n if (appendixIds.includes(5) && appendixIds.includes(6)) { appendicesToShow.push(4); }\r\n else if (appendixIds.includes(5)) { appendicesToShow.push(5); }\r\n else if (appendixIds.includes(6)) { appendicesToShow.push(6); }\r\n\r\n //AUTO\r\n if (appendixIds.includes(8) && appendixIds.includes(9)) { appendicesToShow.push(7); }\r\n else if (appendixIds.includes(8)) { appendicesToShow.push(8); }\r\n else if (appendixIds.includes(9)) { appendicesToShow.push(9); }\r\n\r\n //ENERGIZE\r\n if (appendixIds.includes(11) && appendixIds.includes(12)) { appendicesToShow.push(10); }\r\n else if (appendixIds.includes(11)) { appendicesToShow.push(11); }\r\n else if (appendixIds.includes(12)) { appendicesToShow.push(12); }\r\n\r\n return appendicesToShow;\r\n }\r\n\r\n}\r\n","import { FormatNumberWithCommas } from \"../../../../../../shared/scripts/number-functions\";\r\nimport { TableColumnGroup, TableColumnVisibilityController } from \"../../../../../../shared/scripts/table-column-visibility\";\r\nimport { RenderDateMDY, RenderDateYMD, RenderNullableNumberWithSeparators, RenderNullableString } from \"../../../../../../shared/scripts/table-functions\";\r\nimport { MilestonesBySite } from \"../../scripts/site-activation.entities\";\r\nimport { SiteActivationLookup } from \"../../scripts/site-activation.lookup\";\r\n\r\nexport class SiteLevelMilestoneDetailsTab {\r\n\r\n tableId: string = \"site-milestone-details-table\";\r\n tableWrapperId: string = this.tableId + \"_wrapper\";\r\n dataTable: any;\r\n tableColumnController: TableColumnVisibilityController;\r\n\r\n constructor(milestonesBySite: MilestonesBySite[], milestoneIdsToShow: number[]) {\r\n\r\n let columns: any[] = [\r\n { data: { _: \"SiteNameDisplay\", sort: \"SiteName\" }, title: \"Site Name\", className: \"text-left font-size12\" },\r\n { data: { _: \"SiteNumberDisplay\", sort: \"SiteNumber\" }, title: \"Site Id\", className: \"site-id text-center font-size12 nowrap\" },\r\n { data: \"PrimeContract\", title: \"Platform\", className: \"prime-contract text-center font-size12 nowrap\" },\r\n { data: \"PI\", title: \"Investigator\", className: \"contacts text-center font-size12 nowrap\", render: RenderNullableString }\r\n ];\r\n\r\n let tableColumnGroups: TableColumnGroup[] = [\r\n new TableColumnGroup(\"Site Id\", true, [], \"site-id\"),\r\n new TableColumnGroup(\"Platform\", true, [], \"prime-contract\"),\r\n new TableColumnGroup(\"Investigator\", true, [], \"contacts\"),\r\n new TableColumnGroup(\"Selection\", true, [], \"site-selection\"),\r\n new TableColumnGroup(\"Contracts\", true, [], \"site-contracts\"),\r\n new TableColumnGroup(\"Initiation\", true, [], \"site-initiation\"),\r\n new TableColumnGroup(\"Activation\", true, [], \"site-activation\"),\r\n ];\r\n\r\n milestoneIdsToShow.forEach((id: number) => {\r\n let name: string = SiteActivationLookup.GetMilestone(id);\r\n let css_name: string = \"\";\r\n if (id == 1 || id == 6 || id == 22 || id == 25) { css_name = \"site-selection\"; }\r\n else if (id == 32 || id == 24 || id == 33 || id == 30 || id == 34 || id == 26) { css_name = \"site-contracts\"; }\r\n else if (id == 27 || id == 8 || id == 9 || id == 10 || id == 14 || id == 31 || id == 16 || id == 36) { css_name = \"site-initiation\"; }\r\n else if (id == 17 || id == 19 || id == 35) { css_name = \"site-activation\"; }\r\n\r\n if (css_name.length > 0) {\r\n columns.push({ data: \"MilestoneCompletionDates.\" + id, title: name.split(\" \").join(\"
\"), className: (css_name + \" text-center font-size12 nowrap\"), render: RenderDateMDY, defaultContent: '' });\r\n }\r\n });\r\n\r\n columns.push({ data: \"RandomizedCount\", title: \"Participants
Randomized\", className: \"site-activation text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators });\r\n\r\n this.dataTable = $('#' + this.tableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: true,\r\n pagingType: 'simple',\r\n pageLength: 50,\r\n search: true,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 1\r\n },\r\n columns: columns,\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n }\r\n ],\r\n //order: [[1, 'asc'], [7, 'asc']],\r\n data: milestonesBySite\r\n });\r\n\r\n $(\"#\" + this.tableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").empty().html('');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .spacer\").addClass('col-12 col-md-3');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").addClass('col-12 col-md-3 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('
');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.tableId, (this.tableId + '-column-select'), tableColumnGroups);\r\n\r\n $('#' + this.tableId).on('search.dt', (e, settings) => {\r\n this.setCountFoundLabel(this.dataTable.rows({ search: 'applied' }).count());\r\n });\r\n this.setCountFoundLabel(milestonesBySite.length);\r\n }\r\n\r\n setCountFoundLabel(count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + this.tableId + '-found-count').text(\"No Sites Found\"); break;\r\n case 1: $('#' + this.tableId + '-found-count').text(\"1 Site Found\"); break;\r\n default: $('#' + this.tableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Sites Found\");\r\n }\r\n }\r\n\r\n refresh(): void {\r\n this.dataTable.columns.adjust();\r\n this.tableColumnController.refresh();\r\n }\r\n\r\n updateContent(milestonesBySite: MilestonesBySite[]): void {\r\n this.dataTable.clear();\r\n this.dataTable.rows.add(milestonesBySite).draw();\r\n this.setCountFoundLabel(milestonesBySite.length);\r\n }\r\n\r\n clearSearchBar(): void {\r\n this.dataTable.search('').draw();\r\n }\r\n}","import { FinancialGraphData } from './financials.entities' \r\nimport { TableColumnGroup, TableColumnVisibilityController } from \"../../../../shared/scripts/table-column-visibility\";\r\nimport { FormatNumberWithCommas } from \"../../../../shared/scripts/number-functions\";\r\n\r\nvar Highcharts = require('highcharts/highcharts');\r\nrequire('highcharts/modules/drilldown')(Highcharts);\r\n\r\nexport class FinancialCharts {\r\n\r\n expendituresChart: any;\r\n lastInvoiceDateChart: any;\r\n performedDrilldown: boolean;\r\n chartCategories: string[];\r\n\r\n tableId: string = \"subcontract-table\";\r\n tableWrapperId: string = this.tableId + \"_wrapper\";\r\n dataTable: any;\r\n tableColumnController: TableColumnVisibilityController;\r\n allSubcontracts: FinancialGraphData[];\r\n currentSubcontracts: FinancialGraphData[];\r\n\r\n containerId: string = \"#financial-summary-by-study\";\r\n constructor() {\r\n Highcharts.setOptions({\r\n lang: {\r\n drillUpText: '◁ Back',\r\n thousandsSep: ','\r\n }\r\n });\r\n\r\n $('a[data-toggle=\"tab\"]').on('shown.bs.tab', (e) => {\r\n //to setup dropdowns to go downwards\r\n if (e.currentTarget.id == \"summary-tab\") {\r\n ['#subcontract-table-column-filter-dropdown', '#subcontract-vendor-table-column-filter-dropdown'].forEach((id: string) => $(id).selectpicker(\"refresh\"));\r\n }\r\n });\r\n\r\n\r\n this.performedDrilldown = false;\r\n }\r\n\r\n setGraphContent(graphData: FinancialGraphData[]): void {\r\n this.setExpendituresChart(graphData);\r\n this.setLastInvoiceDateChart(graphData);\r\n\r\n this.chartCategories = [];\r\n graphData.forEach((study) => this.chartCategories.push(study.Name));\r\n\r\n this.allSubcontracts = graphData;\r\n this.currentSubcontracts = graphData;\r\n this.setSubcontractDataTable(graphData);\r\n }\r\n\r\n private setExpendituresChart(graphData: FinancialGraphData[]): void {\r\n\r\n $(this.containerId).find('#project-expenditure-graph').empty();\r\n $(this.containerId).find('#project-expenditure-graph').height(this.getChartHeight(graphData.length));\r\n\r\n let series = this.getExpenditureSeries(graphData);\r\n\r\n this.expendituresChart = Highcharts.chart('project-expenditure-graph', {\r\n chart: {\r\n type: 'bar',\r\n events: {\r\n drilldown: (e) => {\r\n $(this.containerId).find('#project-expenditure-graph').height(this.getChartHeight(e.seriesOptions.data.length));\r\n let clickedCategory: string = e.seriesOptions.id.substring(0, e.seriesOptions.id.indexOf(\"-\")).trim();\r\n //this.expendituresChart.setTitle(null, { text: 'Category: ' + clickedCategory });\r\n let undoButton: string = ``\r\n this.expendituresChart.setTitle({ text: `${undoButton} Displaying vendors for: ${clickedCategory}` }, null);\r\n\r\n if (!this.performedDrilldown) {\r\n\r\n this.performedDrilldown = true;\r\n let point: any = this.lastInvoiceDateChart.get('days-since-last-invoice-series').points.find((pt: any) => pt.name == clickedCategory);\r\n if (point) {\r\n point.doDrilldown(false);\r\n } \r\n this.drilldownTable(clickedCategory);\r\n }\r\n\r\n this.expendituresChart.reflow();\r\n this.expendituresChart.redraw();\r\n\r\n $(this.containerId).find(\".chart-drillup-btn\").on('click', () => {\r\n this.drillupTable();\r\n });\r\n //this.fadeLabels(\"#project-expenditure-graph\"); \r\n },\r\n drillup: (e) => {\r\n $(this.containerId).find('#project-expenditure-graph').height(this.getChartHeight(e.seriesOptions.data.length));\r\n //this.expendituresChart.setTitle(null, { text: 'Click a category to learn more' });\r\n this.expendituresChart.setTitle({ text: 'Click a category to learn more' }, null);\r\n\r\n if (this.performedDrilldown) {\r\n this.performedDrilldown = false;\r\n this.lastInvoiceDateChart.drillUp();\r\n }\r\n this.expendituresChart.reflow();\r\n this.expendituresChart.redraw();\r\n\r\n //this.fadeLabels(\"#project-expenditure-graph\");\r\n }\r\n },\r\n zoomType: 'y',\r\n panning: true,\r\n marginTop: 70\r\n },\r\n credits: { enabled: false },\r\n //title: { text: null },\r\n //subtitle: { text: 'Click a category to learn more', align: 'left', style: {color: 'black'} },\r\n title: { text: 'Click a category to learn more', align: 'left', margin: 0, style: { color: 'black', fontSize: '14px' }, useHTML: true },\r\n tooltip: {\r\n valuePrefix: '$',\r\n valueDecimals: 2\r\n //pointFormat: \"$ {point.y:,.2f}\"\r\n },\r\n xAxis: {\r\n type: 'category'\r\n },\r\n yAxis: {\r\n min: 0,\r\n endOnTick: false, // helps with cropping data labels\r\n maxPadding: 0.2, // larger value has more space for data labels (default is 0.05)\r\n //reversedStacks: false,\r\n title: { text: '' },\r\n labels: {\r\n formatter: function () {\r\n if (this.value >= 100000) {\r\n return '$' + (this.value / 1000000) + 'M'\r\n }\r\n else {\r\n return '$' + this.value\r\n }\r\n \r\n }\r\n },\r\n stackLabels: {\r\n enabled: true,\r\n formatter: function () {\r\n if (this.total >= 100000) {\r\n let millionsVal = (this.total / 1000000);\r\n let styledVal = Highcharts.numberFormat(millionsVal, 2, '.', ',');\r\n return \"$\" + styledVal + 'M'\r\n }\r\n else {\r\n let styledVal = Highcharts.numberFormat(this.total, 0, '.', ',');\r\n return \"$\" + styledVal\r\n }\r\n\r\n },\r\n inside: false, //label issues\r\n //crop: false,\r\n //overflow: 'none'\r\n }\r\n },\r\n legend: { reversed: true, verticalAlign: 'top' },\r\n plotOptions: {\r\n series: { stacking: 'normal' },\r\n bar: { borderWidth: 0 }\r\n },\r\n series: series.primarySeries,\r\n drilldown: {\r\n drillUpButton: {\r\n position: {\r\n align: 'left',\r\n y: -40,\r\n x: -40\r\n },\r\n theme: {\r\n display: 'none'\r\n }\r\n },\r\n series: series.drillDownSeries\r\n }\r\n });\r\n }\r\n\r\n private setLastInvoiceDateChart(graphData: FinancialGraphData[]): void {\r\n\r\n $(this.containerId).find('#days-since-last-invoice-graph').empty();\r\n $(this.containerId).find('#days-since-last-invoice-graph').height(this.getChartHeight(graphData.length));\r\n\r\n let series = this.getLastInvoiceDateSeries(graphData);\r\n\r\n this.lastInvoiceDateChart = Highcharts.chart('days-since-last-invoice-graph', {\r\n chart: {\r\n type: 'bar',\r\n events: {\r\n drilldown: (e) => {\r\n $(this.containerId).find('#days-since-last-invoice-graph').height(this.getChartHeight(e.seriesOptions.data.length));\r\n let clickedCategory: string = e.seriesOptions.id.substring(0, e.seriesOptions.id.indexOf(\"-\")).trim(); \r\n //this.lastInvoiceDateChart.setTitle(null, { text: 'Category: ' + clickedCategory });\r\n\r\n if (!this.performedDrilldown) {\r\n this.performedDrilldown = true;\r\n //this.expendituresChart.xAxis[0].drilldownCategory(e.category);\r\n\r\n let clickedIndex = this.chartCategories.findIndex((category) => category == clickedCategory);\r\n if (clickedIndex != -1) {\r\n this.expendituresChart.xAxis[0].drilldownCategory(clickedIndex);\r\n this.drilldownTable(clickedCategory);\r\n }\r\n }\r\n\r\n this.fadeLabels(\"#days-since-last-invoice-graph\");\r\n this.fadeLabels(\"#project-expenditure-graph\");\r\n this.lastInvoiceDateChart.reflow();\r\n },\r\n drillup: (e) => {\r\n $(this.containerId).find('#days-since-last-invoice-graph').height(this.getChartHeight(e.seriesOptions.data.length));\r\n //this.lastInvoiceDateChart.setTitle(null, { text: 'Click a category to learn more' });\r\n\r\n if (this.performedDrilldown) {\r\n this.performedDrilldown = false;\r\n this.expendituresChart.drillUp();\r\n }\r\n\r\n this.fadeLabels(\"#days-since-last-invoice-graph\");\r\n this.fadeLabels(\"#project-expenditure-graph\");\r\n this.lastInvoiceDateChart.reflow();\r\n }\r\n },\r\n zoomType: 'y',\r\n panning: true,\r\n marginTop: 70\r\n },\r\n\r\n credits: { enabled: false },\r\n title: { text: '-', align: 'left', margin: 0, style: { color: 'transparent', fontSize: '14px' }, useHTML: true },\r\n //subtitle: { text: 'Click a category to learn more', align: 'left' },\r\n xAxis: {\r\n type: 'category'\r\n },\r\n yAxis: {\r\n min: 0,\r\n endOnTick: false, // helps with cropping data labels\r\n maxPadding: 0.2, // larger value has more space for data labels (default is 0.05)\r\n //reversedStacks: false,\r\n title: { text: '' },\r\n labels: {\r\n //formatter: function () {\r\n // //return '$' + this.axis.defaultLabelFormatter.call(this);\r\n // return '$' + (this.value / 1000000) + 'M'\r\n //}\r\n },\r\n stackLabels: {\r\n enabled: true,\r\n formatter: function () {\r\n return Highcharts.numberFormat(this.total, 0, '.', ','); \r\n },\r\n inside: false, //label issues\r\n //crop: false,\r\n //overflow: 'none'\r\n }\r\n },\r\n legend: { reversed: true, verticalAlign: 'top' },\r\n plotOptions: {\r\n series: { stacking: 'normal' },\r\n bar: { borderWidth: 0 }\r\n },\r\n series: series.primarySeries,\r\n drilldown: {\r\n drillUpButton: {\r\n position: {\r\n y: -40\r\n },\r\n theme: {\r\n display: 'none'\r\n }\r\n },\r\n series: series.drilldownSeries\r\n }\r\n });\r\n }\r\n\r\n\r\n private setSubcontractDataTable(data: FinancialGraphData[]): void {\r\n\r\n let chart = this;\r\n let columns: any[] = [\r\n { data: { _: \"Id\", display: \"StudyNameDisplay\" }, title: \"Category\", className: \"sub-category text-left font-size12 nowrap\", orderable: false },\r\n { data: { _: \"Name\", display: \"VendorNameDisplay\" }, title: \"Vendor\", className: \"sub-vendor text-left font-size12 nowrap\" },\r\n { data: \"SubcontractNumber\", title: \"Subcontract #\", className: \"sub-number text-left font-size12 nowrap\" },\r\n { data: \"POID\", title: \"PO/Vendor
ID\", className: \"sub-poid text-center font-size12 nowrap\" },\r\n { data: \"ProjectLevel\", title: \"Project
Code\", className: \"sub-project-code text-center font-size12 nowrap\" },\r\n { data: { _: \"SubcontractStartDateCompare\", display: \"SubcontractStartDate\" }, title: \"Subcontract
Start Date\", className: \"sub-subcontract-start-date text-center font-size12 nowrap\" },\r\n { data: { _: \"SubcontractEndDateCompare\", display: \"SubcontractEndDate\" }, title: \"Subcontract
End Date\", className: \"sub-subcontract-end-date text-center font-size12 nowrap\" },\r\n { data: \"SubcontractType\", title: \"Subcontract
Type\", className: \"sub-subcontract-type text-center font-size12 nowrap\" },\r\n\r\n { data: { _: \"TotalSubcontractAmount\", display: \"TotalSubcontractAmountDisplay\" }, title: \"Total
Subcontract
Amount\", className: \"sub-subcontract-amount text-center font-size12 nowrap\" },\r\n { data: { _: \"TotalRestrictedFunds\", display: \"TotalRestrictedFundsDisplay\" }, title: \"Total
Restricted
Funds\", className: \"sub-restricted-funds text-center font-size12 nowrap\" },\r\n { data: { _: \"TotalUnrestrictedFunds\", display: \"TotalUnrestrictedFundsDisplay\" }, title: \"Total
Unrestricted
Funds\", className: \"sub-unrestricted-funds text-center font-size12 nowrap\" },\r\n { data: { _: \"RemainingUnrestrictedFunds\", display: \"RemainingUnrestrictedFundsDisplay\" }, title: \"Remaining
Unrestricted
Funds\", className: \"sub-remaining-unrestricted text-center font-size12 nowrap\" },\r\n\r\n { data: { _: \"TotalInvoicedToDate\", display: \"TotalInvoicedToDateDisplay\" }, title: \"Total
Invoiced
To Date\", className: \"sub-invoiced-to-date text-center font-size12 nowrap\" },\r\n { data: { _: \"LastInvoiceDateCompare\", display: \"LastInvoiceDate\" }, title: \"Last
Invoice
Date\", className: \"sub-last-invoice-date text-center font-size12 nowrap\" },\r\n //{ data: \"StudyName\", title: \"Study Name\", visble: false, className: \"d-none\" }\r\n ];\r\n\r\n let tableColumnGroups: TableColumnGroup[] = [\r\n\r\n //new TableColumnGroup(\"Category\", true, [], \"sub-category\"),\r\n new TableColumnGroup(\"Vendor\", false, [], \"sub-vendor\"), // auto show on drilldown\r\n new TableColumnGroup(\"Subcontract #\", false, [], \"sub-number\"),\r\n new TableColumnGroup(\"PO/Vendor ID\", false, [], \"sub-poid\"),\r\n new TableColumnGroup(\"Project Code\", false, [], \"sub-project-code\"),\r\n new TableColumnGroup(\"Subcontract Start Date\", false, [], \"sub-subcontract-start-date\"),\r\n new TableColumnGroup(\"Subcontract End Date\", false, [], \"sub-subcontract-end-date\"), // auto show on drilldown\r\n new TableColumnGroup(\"Subcontract Type\", false, [], \"sub-subcontract-type\"), // auto show on drilldown\r\n\r\n new TableColumnGroup(\"Total Subcontract Amount\", true, [], \"sub-subcontract-amount\"),\r\n new TableColumnGroup(\"Total Restricted Funds\", true, [], \"sub-restricted-funds\"),\r\n new TableColumnGroup(\"Total Unrestricted Funds\", true, [], \"sub-unrestricted-funds\"),\r\n new TableColumnGroup(\"Remaining Unrestricted Funds\", true, [], \"sub-remaining-unrestricted\"),\r\n new TableColumnGroup(\"Total Invoiced to Date\", true, [], \"sub-invoiced-to-date\"),\r\n new TableColumnGroup(\"Last Invoice Date\", true, [], \"sub-last-invoice-date\")\r\n ];\r\n\r\n this.dataTable = $('#' + this.tableId).DataTable({\r\n \"dom\": '<\"top-controls\"<\"column-select\"><\"added-since\"><\"search-bar\"f><\"spacer\"><\"count-found\"B>>rtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: true,\r\n pagingType: 'simple',\r\n pageLength: 50,\r\n search: true,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n /*fixedColumns: {\r\n leftColumns: 1\r\n },*/\r\n columns: columns,\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n title: \"RECOVER-CT Financials Export\"\r\n //exportOptions: {\r\n // columns: [1, 2, 3, 4]\r\n //}\r\n },\r\n {\r\n extend: 'csv',\r\n text: 'Export All',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n title: \"RECOVER-CT Financials Export All\",\r\n customize: function (csv) {\r\n let allExportItems = [];\r\n chart.allSubcontracts.map(item => {\r\n item.VendorNameDisplay = \"TOTAL\"\r\n allExportItems.push(item);\r\n item.DrillDown.map(drillDownItem => {\r\n allExportItems.push(drillDownItem);\r\n })\r\n });\r\n let modifiedCSV = chart.convertToCSV(allExportItems);\r\n return modifiedCSV;\r\n }\r\n }\r\n ],\r\n order: [0, 'asc'],\r\n data: this.allSubcontracts,\r\n initComplete: (settings, json) => {\r\n // Set up Drillup Button\r\n $(this.containerId).find(\"th.sub-category\").html(`\r\n \r\n
\r\n \r\n
\r\n
Category
\r\n
\r\n `);\r\n\r\n $(this.containerId).find(\".table-drillup-btn\").on('click', () => {\r\n this.drillupTable();\r\n });\r\n\r\n // Set up Drilldown\r\n let charts = this;\r\n $(this.containerId).find(\".table-drilldown-btn\").on('click', function () {\r\n // Drilldown the charts\r\n let clickedCategory = $(this).text();\r\n let clickedIndex = charts.chartCategories.findIndex((category) => category == clickedCategory);\r\n charts.expendituresChart.xAxis[0].drilldownCategory(clickedIndex);\r\n\r\n // Update the Table\r\n charts.drilldownTable(clickedCategory);\r\n });\r\n }\r\n });\r\n\r\n $(\"#\" + this.tableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").addClass('col-12 col-md-3 px-0');\r\n $(\"#\" + this.tableWrapperId + \" .column-select\").empty().html('');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n $(\"#\" + this.tableWrapperId + \" .spacer\").addClass('col-12 col-md-1');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").addClass('col-12 col-md-5 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('
');\r\n $(\"#\" + this.tableWrapperId + \" .count-found\").prepend('');\r\n\r\n this.tableColumnController = new TableColumnVisibilityController(this.tableId, (this.tableId + '-column-select'), tableColumnGroups);\r\n\r\n $('#' + this.tableId).on('search.dt', (e, settings) => {\r\n this.setCountFoundLabel(this.dataTable.rows({ search: 'applied' }).count());\r\n });\r\n\r\n $(\"#\" + this.tableWrapperId).find(\"th.sorting\").on('click', () => {\r\n this.dataTable.columns.adjust().draw();\r\n })\r\n \r\n this.setCountFoundLabel(this.allSubcontracts.length);\r\n\r\n //this.initializeTableLinks();\r\n this.dataTable.on('draw', (e, settings) => {\r\n this.initializeTableLinks();\r\n });\r\n\r\n $(\"#summary-tab\").on('click', () => {\r\n setTimeout(() => {\r\n this.dataTable.columns.adjust().draw();\r\n }, 500)\r\n }); \r\n\r\n $(\"#summary-chart-switch\").on('change', () => {\r\n setTimeout(() => {\r\n this.dataTable.columns.adjust().draw();\r\n }, 250)\r\n });\r\n }\r\n\r\n private setCountFoundLabel(count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + this.tableId + '-found-count').text(\"No Entries Found\"); break;\r\n case 1: $('#' + this.tableId + '-found-count').text(\"1 Entry Found\"); break;\r\n default: $('#' + this.tableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Entries Found\");\r\n }\r\n }\r\n\r\n private initializeTableLinks() {\r\n let charts = this;\r\n\r\n $(this.containerId).find(\".table-drilldown-btn\").off('click');\r\n $(this.containerId).find(\".table-drilldown-btn\").on('click', function() {\r\n // Drilldown the charts\r\n let clickedCategory = $(this).text();\r\n let clickedIndex = charts.chartCategories.findIndex((category) => category == clickedCategory);\r\n charts.expendituresChart.xAxis[0].drilldownCategory(clickedIndex);\r\n });\r\n }\r\n\r\n private drilldownTable(category: string): void {\r\n $(this.containerId).find(\".table-drillup-container\").removeClass(\"d-none\");\r\n let subcontracts = this.allSubcontracts.find(subcontract => subcontract.StudyName == category).DrillDown;\r\n if (!subcontracts) {\r\n return\r\n }\r\n this.currentSubcontracts = subcontracts;\r\n\r\n // Gives you selected column Names\r\n let selectedColumnNames: string[] = $(`#${this.tableId}-column-filter-dropdown`).val(); \r\n\r\n // Gives you all the names of the datatable\r\n let columnHeaders = this.dataTable.columns().header().toArray();\r\n let allColumnNames = [];\r\n columnHeaders.forEach(header => {\r\n let columnName = $(header).html().replace(\"
\", \" \").replace(\"
\", \" \").trim();\r\n allColumnNames.push(columnName);\r\n });\r\n\r\n // Get Selected Column Indexes\r\n let selectedColumnIndexes = [1,6,7] // Defaults\r\n for (let i = 0; i < allColumnNames.length; i++) \r\n {\r\n if (selectedColumnNames.indexOf(allColumnNames[i]) != -1 && selectedColumnIndexes.indexOf(i) == -1) {\r\n selectedColumnIndexes.push(i);\r\n }\r\n }\r\n\r\n // Show columns for drilldown \r\n this.dataTable.columns(selectedColumnIndexes).visible(true, false);\r\n \r\n\r\n this.updateTableContent(this.currentSubcontracts);\r\n this.clearSearchBar();\r\n }\r\n\r\n private drillupTable(): void {\r\n // Drilldown the charts\r\n $(this.containerId).find(\".table-drillup-container\").addClass(\"d-none\");\r\n this.expendituresChart.drillUp();\r\n\r\n // Hide unused columns for top level\r\n this.dataTable.columns([1, 2, 3, 4, 5, 6, 7]).visible(false, false); // Blank for top level\r\n\r\n // Update the Tables\r\n this.updateTableContent(this.allSubcontracts);\r\n this.clearSearchBar();\r\n }\r\n\r\n private updateTableContent(subcontracts: FinancialGraphData[]): void {\r\n this.dataTable.clear();\r\n this.dataTable.rows.add(subcontracts);\r\n this.dataTable.order([0, 'asc']).draw();\r\n\r\n this.setCountFoundLabel(subcontracts.length);\r\n\r\n setTimeout(() => {\r\n //this.dataTable.order([0, 'asc']);\r\n this.dataTable.columns.adjust().draw();\r\n }, 250)\r\n \r\n }\r\n\r\n\r\n private getChartHeight(entries: number): number {\r\n let value: number = 150 + (entries * 30 * 1.5);\r\n return value;\r\n }\r\n\r\n\r\n\r\n private getExpenditureSeries(data: FinancialGraphData[]) {\r\n let dataLabels: string[] = ['Committed Restricted', 'Remaining Unrestricted', 'Invoiced To-Date']; // Readable Data Labels\r\n let dataLabelIds: string[] = ['reserved-series', 'unreserved-series', 'invoiced-series']; // Data Label IDs\r\n let dataFields: string[] = ['CommittedRestricted', 'RemainingUnrestricted', 'InvoicedToDate']; // FinanacialGraphData fields mapped to Data Labels\r\n let seriesColors: string[] = ['#6e6e6e', '#77a6b6 ', '#E9C46A ']; // Colors for each data label\r\n\r\n let drillDownSeries = [];\r\n let primarySeries = dataLabels.map((label: string, index: number) => {\r\n let primaryData: any[] = [];\r\n data.forEach((study: FinancialGraphData) => {\r\n\r\n primaryData.push({ \r\n name: study.Name, // Study Name\r\n y: study[dataFields[index]], // Amount of Money (per category)\r\n drilldown: study.Name + \"-\" + label // id of relative drilldown\r\n });\r\n\r\n let filteredRecords: FinancialGraphData[] = study.DrillDown; // Get vendors and other folks assoicated with study\r\n let detailedDrilldownData: any[] = [];\r\n filteredRecords.forEach((record: FinancialGraphData) => {\r\n detailedDrilldownData.push(\r\n [record.Name, record[dataFields[index]]] // [Vendor Name, Amount of Money (per category)]\r\n );\r\n });\r\n\r\n drillDownSeries.push({\r\n name: label, // Data Label\r\n id: study.Name + \"-\" + label, // id to help drilldown\r\n data: detailedDrilldownData // [Vendor Name, Amount of Money]\r\n });\r\n });\r\n\r\n return {\r\n id: dataLabelIds[index],\r\n name: label,\r\n data: primaryData,\r\n //drilldown: label,\r\n color: seriesColors[index],\r\n };\r\n });\r\n\r\n return {\r\n primarySeries: primarySeries,\r\n drillDownSeries: drillDownSeries\r\n };\r\n }\r\n\r\n private getLastInvoiceDateSeries(graphData: FinancialGraphData[]) {\r\n let drilldownLastInvoicedSeries: any[] = [];\r\n let invoiceData: any[] = graphData.map((study: FinancialGraphData) => {\r\n\r\n let filteredRecords = study.DrillDown;\r\n let detailedDrilldownData: any[] = [];\r\n filteredRecords.forEach((record: FinancialGraphData) => {\r\n detailedDrilldownData.push(\r\n [record.Name, record.DaysSinceLastInvoice]\r\n );\r\n });\r\n\r\n drilldownLastInvoicedSeries.push({\r\n name: \"Days Since Last Invoice\",\r\n id: study.Name + \"-\",\r\n data: detailedDrilldownData\r\n });\r\n\r\n return {\r\n name: study.Name,\r\n y: study.DaysSinceLastInvoice,\r\n drilldown: study.Name + \"-\"\r\n };\r\n });\r\n\r\n let primarySeries = [{\r\n id: 'days-since-last-invoice-series',\r\n name: \"Days Since Last Invoice\",\r\n data: invoiceData,\r\n //color: \"#152850\",\r\n color: \"#E9C46A\"\r\n }];\r\n\r\n return {\r\n primarySeries: primarySeries,\r\n drilldownSeries: drilldownLastInvoicedSeries \r\n }\r\n }\r\n\r\n private fadeLabels(chartId: string): void {\r\n let stackLabel = $(chartId).find('.highcharts-stack-labels').first();\r\n stackLabel.addClass(\"d-none\");\r\n stackLabel.removeClass(\"show-label\");\r\n setTimeout(() => {\r\n stackLabel.addClass(\"show-label\");\r\n stackLabel.removeClass(\"d-none\");\r\n this.hideExcessLabels(chartId);\r\n }, 20);\r\n \r\n }\r\n\r\n private hideExcessLabels(chartId: string): void {\r\n let stackLabels = $(chartId).find('.highcharts-stack-labels').children('.highcharts-label');\r\n let isHidden = false;\r\n if (stackLabels.length > 0) {\r\n if (stackLabels[0].getAttribute('visibility') == 'hidden') {\r\n return;\r\n }\r\n }\r\n for (let i = 0; i < stackLabels.length; i++) {\r\n // if the label is hidden\r\n if (stackLabels[i].getAttribute('visibility') == 'hidden') {\r\n isHidden = true;\r\n }\r\n\r\n if (isHidden && stackLabels[i].getAttribute('visibility') == 'visible') {\r\n //console.log(\"Hide This:\")\r\n //console.log(stackLabels[i]);\r\n stackLabels[i].setAttribute('visibility', 'hidden');\r\n }\r\n } \r\n }\r\n\r\n private convertToCSV(objArray: FinancialGraphData[]): string {\r\n let csvArray = [\r\n [\r\n \"\\\"Category\\\"\",\r\n \"\\\"Vendor\\\"\",\r\n \"\\\"Subcontract #\\\"\",\r\n \"\\\"PO/Vendor ID\\\"\",\r\n \"\\\"Project Code\\\"\",\r\n \"\\\"Subcontract Start Date\\\"\",\r\n \"\\\"Subcontract End Date\\\"\",\r\n \"\\\"Subcontract Type\\\"\",\r\n \"\\\"Total Subcontract Amount\\\"\",\r\n \"\\\"Total Restricted Funds\\\"\",\r\n \"\\\"Total Unrestricted Funds\\\"\",\r\n \"\\\"Remaining Unrestricted Funds\\\"\",\r\n \"\\\"Total Invoiced To Date\\\"\",\r\n \"\\\"Last Invoice Date\\\"\"\r\n ],\r\n \r\n ...objArray.map(item => [\r\n `\\\"${item.StudyName}\\\"`,\r\n `\\\"${item.VendorNameDisplay}\\\"`,\r\n `\\\"${item.SubcontractNumber}\\\"`,\r\n `\\\"${item.POID}\\\"`,\r\n `\\\"${item.ProjectLevel}\\\"`,\r\n `\\\"${item.SubcontractStartDate}\\\"`,\r\n `\\\"${item.SubcontractEndDate}\\\"`,\r\n `\\\"${item.SubcontractType}\\\"`,\r\n `\\\"${item.TotalSubcontractAmountDisplay}\\\"`,\r\n `\\\"${item.TotalRestrictedFundsDisplay}\\\"`,\r\n `\\\"${item.TotalUnrestrictedFundsDisplay}\\\"`,\r\n `\\\"${item.RemainingUnrestrictedFundsDisplay}\\\"`,\r\n `\\\"${item.TotalInvoicedToDateDisplay}\\\"`,\r\n `\\\"${item.LastInvoiceDate}\\\"`\r\n ])\r\n \r\n ];\r\n let csvJoined = csvArray.map(item => item.join(\",\"));\r\n let csvString = csvJoined.join(\"\\n\");\r\n \r\n return csvString;\r\n }\r\n\r\n clearSearchBar(): void {\r\n this.dataTable.search('').draw();\r\n }\r\n\r\n}","import { OutcomeEvent } from \"../scripts/outcomes.entities\";\r\n\r\n\r\nexport class OutcomesDatatableEntry {\r\n Study: string;\r\n Appendix: string;\r\n Site: string;\r\n SiteNumber: string;\r\n Outcome: string;\r\n\r\n PotentialOutcomes: number;\r\n ExpectedOutcomes: number;\r\n Completed: number[] = [];\r\n Overdue: number[] = [];\r\n NotDone: number[] = [];\r\n DiscPrior: number[] = [];\r\n NotYetDue: number;\r\n\r\n CompletedOutcomes: OutcomeEvent[] = [];\r\n OverdueOutcomes: OutcomeEvent[] = [];\r\n NotDoneOutcomes: OutcomeEvent[] = [];\r\n DiscPriorOutcomes: OutcomeEvent[] = [];\r\n\r\n sortValue: number;\r\n\r\n constructor(study: string, appendix: string, site: string, siteNumber: string, outcome: string) {\r\n this.Study = study;\r\n this.Appendix = appendix;\r\n this.Site = site;\r\n this.SiteNumber = siteNumber;\r\n this.Outcome = outcome;\r\n\r\n this.PotentialOutcomes = 0;\r\n this.ExpectedOutcomes = 0;\r\n this.NotYetDue = 0;\r\n }\r\n\r\n addOutcomeEvent(outcome: OutcomeEvent): void {\r\n this.PotentialOutcomes += 1;\r\n\r\n switch (outcome.OutcomeStatusId) {\r\n case 1: this.Completed[0] = (this.Completed[0] == undefined) ? 1 : (this.Completed[0] + 1); this.ExpectedOutcomes += 1; this.CompletedOutcomes.push(outcome); break;\r\n case 2: this.DiscPrior[0] = (this.DiscPrior[0] == undefined) ? 1 : (this.DiscPrior[0] + 1); this.ExpectedOutcomes += 1; this.DiscPriorOutcomes.push(outcome); break;\r\n case 3: this.Overdue[0] = (this.Overdue[0] == undefined) ? 1 : (this.Overdue[0] + 1); this.ExpectedOutcomes += 1; this.OverdueOutcomes.push(outcome); break;\r\n case 4: this.NotDone[0] = (this.NotDone[0] == undefined) ? 1 : (this.NotDone[0] + 1); this.ExpectedOutcomes += 1; this.NotDoneOutcomes.push(outcome); break;\r\n case 5: this.NotYetDue = (this.NotYetDue == undefined) ? 1 : (this.NotYetDue + 1); break;\r\n }\r\n }\r\n\r\n initialize(): void {\r\n if (this.ExpectedOutcomes > 0) {\r\n if (this.Completed[0] != undefined) { this.Completed[1] = (this.Completed[0] / this.ExpectedOutcomes); }\r\n if (this.Overdue[0] != undefined) { this.Overdue[1] = (this.Overdue[0] / this.ExpectedOutcomes); }\r\n if (this.NotDone[0] != undefined) { this.NotDone[1] = (this.NotDone[0] / this.ExpectedOutcomes); }\r\n if (this.DiscPrior[0] != undefined) { this.DiscPrior[1] = (this.DiscPrior[0] / this.ExpectedOutcomes); }\r\n }\r\n }\r\n\r\n static addValue(currentValue: number, valueToAdd: number): number {\r\n if (currentValue == undefined) { currentValue = 0; }\r\n currentValue += valueToAdd;\r\n return currentValue;\r\n }\r\n\r\n static getHash(grouping: string, outcome: OutcomeEvent): string {\r\n if (grouping == \"none\") { return outcome.StudyId + \"-\" + outcome.AppendixId + \"-\" + outcome.SiteNumber + \"-\" + outcome.OutcomeId; }\r\n else if (grouping == \"by-outcome\") { return outcome.StudyId + \"-\" + outcome.AppendixId + \"-\" + outcome.OutcomeId; }\r\n else if (grouping == \"by-site\") { return outcome.StudyId + \"-\" + outcome.AppendixId + \"-\" + outcome.SiteNumber; }\r\n else return \"unknown\";\r\n }\r\n}","import { plainToClass } from \"class-transformer\";\r\nimport { DateUpdatedSection } from \"../../../../shared/components/date-updated-section/date-updated-section.view\";\r\nimport { Unique } from \"../../../../shared/scripts/array-functions\";\r\nimport { IFilter } from \"../../../../shared/scripts/filtering/filtering.entities\";\r\nimport { GetSelectedDropdownValues, InitializeFilterDropdown } from \"../../../../shared/scripts/filtering/filtering.utilities\";\r\nimport { SiteInfo } from \"../../sites/shared/sites.entities\";\r\nimport { PrescreeningChart } from \"./prescreening.chart\";\r\nimport { PrescreeningEntry, PrescreeningGraphDataPoint } from \"./prescreening.entities\";\r\nimport { PrescreeningTable } from \"./prescreening.table\";\r\n\r\n\r\n\r\nexport class PrescreeningPage implements IFilter {\r\n\r\n summaryData: PrescreeningEntry[] = [];\r\n //summaryMap: Map = new Map();\r\n \r\n //chart: PrescreeningChart;\r\n table: PrescreeningTable;\r\n\r\n constructor(DataJson: any, SiteInfoJson: any, LastUpdatedJson: any) {\r\n\r\n $(() => {\r\n\r\n new DateUpdatedSection(LastUpdatedJson);\r\n\r\n let siteDictionary: Map = SiteInfo.CreateNameMap(SiteInfoJson, undefined);\r\n\r\n let data: PrescreeningEntry[] = plainToClass(PrescreeningEntry, DataJson);\r\n //data = data.filter((el: PrescreeningEntry) => el.SiteNumber == 226);\r\n\r\n let weeklyData: PrescreeningEntry[] = [];\r\n\r\n data.forEach((element: PrescreeningEntry) => {\r\n element.SiteName = siteDictionary.get(element.SiteNumber);\r\n element.initialize();\r\n if (element.Type == 0) {\r\n this.summaryData.push(element);\r\n //this.summaryMap.set(element.getSeriesName(), element);\r\n }\r\n else if (element.Type == 1) { weeklyData.push(element); }\r\n else if (element.Type == 2) {\r\n this.summaryData.push(element);\r\n //console.log(element); \r\n }\r\n });\r\n\r\n this.summaryData.forEach((summary: PrescreeningEntry) => {\r\n summary.WeeklyEntries = weeklyData.filter((entry: PrescreeningEntry) => summary.SiteHash == entry.SiteHash);\r\n summary.createChartSeries(); \r\n });\r\n\r\n InitializeFilterDropdown('#prime-contract-dropdown', Unique(this.summaryData.map((entry: PrescreeningEntry) => entry.PrimeContract), true), this);\r\n\r\n $('#loading-indicator').hide();\r\n $('.loaded-content').removeClass('invisible');\r\n\r\n //this.chart = new PrescreeningChart(this.summaryMap);\r\n //this.chart = new PrescreeningChart(new Map(this.summaryData.map((entry: PrescreeningEntry) => [entry.ChartSeriesName, entry])));\r\n //this.table = new PrescreeningTable(this.summaryData, GetSelectedDropdownValues('#prime-contract-dropdown'), this.chart);\r\n this.table = new PrescreeningTable(this.summaryData, GetSelectedDropdownValues('#prime-contract-dropdown'));\r\n\r\n //console.log(this.data);\r\n //console.log(SiteDictionaryJson);\r\n this.triggerFilter();\r\n \r\n });\r\n }\r\n\r\n triggerFilter(): void {\r\n\r\n let selectedPrimeContracts: string[] = GetSelectedDropdownValues('#prime-contract-dropdown');\r\n\r\n if (selectedPrimeContracts.length == 0) {\r\n $('#page-content').hide();\r\n }\r\n else {\r\n $('#page-content').show();\r\n this.table.updateData(selectedPrimeContracts);\r\n }\r\n }\r\n}","import { FormatNumberWithCommas } from \"../../../../../../shared/scripts/number-functions\";\r\nimport { SetActiveTab } from \"../../../../../../shared/scripts/tab-utilities\";\r\nimport { RenderNullableNumberWithSeparators, RenderProgressValue } from \"../../../../../../shared/scripts/table-functions\";\r\nimport { ISiteInfo } from \"../../../scripts/data-completeness.page\";\r\nimport { VisitsController } from \"../scripts/visits.controller\";\r\nimport { VisitsDialog } from \"../scripts/visits.dialog\";\r\nimport { VisitsDatatableEntry } from \"./visits-datatable.entities\";\r\n\r\n\r\nexport class VisitsDatatableView {\r\n\r\n selectedGrouping: string; //none, visit, site\r\n\r\n tableNone: any; tableNoneId: string = \"visit-datatable-none\";\r\n tableByVisit: any; tableVisitId: string = \"visit-datatable-byvisit\";\r\n tableBySite: any; tableSiteId: string = \"visit-datatable-bysite\";\r\n\r\n constructor() {\r\n this.selectedGrouping = \"none\";\r\n\r\n this.tableNone = this.initializeTable(this.tableNoneId, this.tableNoneId + \"_wrapper\");\r\n this.tableByVisit = this.initializeTable(this.tableVisitId, this.tableVisitId + \"_wrapper\");\r\n this.tableBySite = this.initializeTable(this.tableSiteId, this.tableSiteId + \"_wrapper\");\r\n\r\n this.initializeDialog([this.tableNoneId, this.tableVisitId, this.tableSiteId]);\r\n }\r\n\r\n\r\n SetView(newView: string): void {\r\n\r\n //console.log(newView);\r\n\r\n if (!newView.startsWith(\"visit-datatable\")) { return; }\r\n\r\n if (newView.includes(\"none\")) { this.selectedGrouping = \"none\"; }\r\n else if (newView.includes(\"byvisit\")) { this.selectedGrouping = \"visit\"; }\r\n else if (newView.includes(\"bysite\")) { this.selectedGrouping = \"site\"; }\r\n\r\n if (this.selectedGrouping == \"none\") { SetActiveTab(\"visit-datatable-tabs\", \"content-visit-datatable-none\"); this.tableNone.columns.adjust(); }\r\n else if (this.selectedGrouping == \"visit\") { SetActiveTab(\"visit-datatable-tabs\", \"content-visit-datatable-byvisit\"); this.tableByVisit.columns.adjust(); }\r\n else if (this.selectedGrouping == \"site\") { SetActiveTab(\"visit-datatable-tabs\", \"content-visit-datatable-bysite\"); this.tableBySite.columns.adjust(); }\r\n }\r\n\r\n UpdateData(tableEntriesNone: VisitsDatatableEntry[], tableEntriesByVisit: VisitsDatatableEntry[], tableEntriesBySite: VisitsDatatableEntry[]): void {\r\n this.tableNone.clear();\r\n this.tableByVisit.clear();\r\n this.tableBySite.clear();\r\n\r\n this.tableNone.rows.add(tableEntriesNone).draw();\r\n this.tableByVisit.rows.add(tableEntriesByVisit).draw();\r\n this.tableBySite.rows.add(tableEntriesBySite).draw();\r\n\r\n //this.setCountFoundLabel(milestonesBySite.length);\r\n }\r\n\r\n private initializeTable(tableId: string, tableWrapperId: string): any {\r\n\r\n let dataTable: any = $('#' + tableId).DataTable({\r\n \"dom\": 'lrtip',\r\n autoWidth: true,\r\n info: false,\r\n paging: false,\r\n //pagingType: 'simple',\r\n //pageLength: 50,\r\n search: false,\r\n scrollX: true,\r\n scrollY: '70vh',\r\n scrollCollapse: true,\r\n orderCellsTop: true,\r\n fixedColumns: {\r\n leftColumns: 4\r\n },\r\n columns: [\r\n { data: \"Study\", title: \"Platform\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Appendix\", title: \"Trial\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"SiteNumber\", title: \"Site\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"Visit\", title: \"Visit\", className: \"text-left font-size12 nowrap\" },\r\n { data: \"PotentialVisits\", title: \"Potential
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators },\r\n { data: \"ExpectedVisits\", title: \"Expected
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators },\r\n { data: \"Completed.0\", title: \"Completed
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators, defaultContent: '', visible: false },\r\n { data: \"Completed.1\", title: \"Percent
Completed\", className: \"text-center font-size12 nowrap gray font-underline clickable-completed\", render: RenderProgressValue, defaultContent: '' },\r\n { data: \"Overdue.0\", title: \"Overdue
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators, defaultContent: '', visible: false },\r\n { data: \"Overdue.1\", title: \"Percent
Overdue\", className: \"text-center font-size12 nowrap gray font-underline clickable-overdue\", render: RenderProgressValue, defaultContent: '' },\r\n { data: \"NotDone.0\", title: \"Not Done
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators, defaultContent: '', visible: false },\r\n { data: \"NotDone.1\", title: \"Percent
Not Done\", className: \"text-center font-size12 nowrap gray font-underline clickable-notdone\", render: RenderProgressValue, defaultContent: '' },\r\n { data: \"DiscPrior.0\", title: \"Disc. Prior
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators, defaultContent: '', visible: false },\r\n { data: \"DiscPrior.1\", title: \"Percent
Disc. Prior\", className: \"text-center font-size12 nowrap gray font-underline clickable-discprior\", render: RenderProgressValue, defaultContent: '' },\r\n { data: \"NotYetDue\", title: \"Not Yet Due
Visits\", className: \"text-center font-size12 nowrap\", render: RenderNullableNumberWithSeparators, defaultContent: '' },\r\n { data: \"Site\", title: \"Site Name\", className: \"text-left font-size10 nowrap\", visible: false },\r\n ],\r\n buttons: [\r\n {\r\n extend: 'csv',\r\n text: '',\r\n titleAttr: 'CSV',\r\n charset: 'utf-8',\r\n }\r\n ],\r\n createdRow: (row, data: any, index) => {\r\n //cell formatting on percentages\r\n if (data.Completed[1] != undefined) { this.setCellPercentValue(row, 'td:eq(6)', this.getCellColorByValue(\"Completed\", data.Completed[1]), data.Completed[0]); }\r\n if (data.Overdue[1] != undefined) { this.setCellPercentValue(row, 'td:eq(7)', this.getCellColorByValue(\"Overdue\", data.Overdue[1]), data.Overdue[0]); }\r\n if (data.NotDone[1] != undefined) { this.setCellPercentValue(row, 'td:eq(8)', this.getCellColorByValue(\"NotDone\", data.NotDone[1]), data.NotDone[0]); }\r\n if (data.DiscPrior[1] != undefined) { this.setCellPercentValue(row, 'td:eq(9)', this.getCellColorByValue(\"DiscPrior\", data.DiscPrior[1]), data.DiscPrior[0]); }\r\n\r\n //tooltip config for percentages and site number\r\n //$(row).attr('title', data.Site);\r\n //$(row).find('td').each(function (index, td) {\r\n // $(td).attr('data-toggle', 'tooltip');\r\n // $(td).attr('data-placement', 'top');\r\n //});\r\n }\r\n //order: [[1, 'asc'], [7, 'asc']],\r\n });\r\n\r\n dataTable.on('column-visibility.dt', function () {\r\n $('[data-toggle=\"tooltip\"]').tooltip()\r\n });\r\n $('[data-toggle=\"tooltip\"]').tooltip()\r\n\r\n //$(\"#\" + tableWrapperId + \" .top-controls\").addClass('row mx-0');\r\n\r\n //$(\"#\" + tableWrapperId + \" .search-bar\").addClass('col-12 col-md-3');\r\n\r\n //$(\"#\" + tableWrapperId + \" .spacer\").addClass('col-12 col-md-3');\r\n //$(\"#\" + tableWrapperId + \" .count-found\").addClass('col-12 col-md-3 d-flex justify-content-end vertical-align-middle align-self-end mt-2 pr-0');\r\n // \r\n //$(\"#\" + tableWrapperId + \" .count-found\").prepend('
');\r\n //$(\"#\" + tableWrapperId + \" .count-found\").prepend('');\r\n\r\n //$('#' + tableId).on('search.dt', (e, settings) => {\r\n // this.setCountFoundLabel(tableId, dataTable.rows({ search: 'applied' }).count());\r\n //});\r\n\r\n return dataTable;\r\n }\r\n\r\n private getCellColorByValue(dataElement: string, value: number): string {\r\n if (dataElement == \"Completed\") {\r\n if (value >= 0.85) return \"#7fb87f\";\r\n else if (value >= 0.65) return \"#FCE77D\";\r\n else return \"#F07D7D\";\r\n }\r\n else if (dataElement == \"Overdue\" || dataElement == \"NotDone\") {\r\n if (value < 0.10) return \"#7fb87f\";\r\n else if (value <= 0.20) return \"#FCE77D\";\r\n else return \"#F07D7D\";\r\n }\r\n else if (dataElement == \"DiscPrior\") {\r\n if (value < 0.10) return \"#7fb87f\";\r\n else if (value <= 0.20) return \"#FCE77D\";\r\n else return \"#F07D7D\";\r\n }\r\n }\r\n\r\n private initializeDialog(tableIds: string[]): void {\r\n\r\n tableIds.forEach((tableId: string) => {\r\n $('#' + tableId).on('click', 'td.clickable-completed', function () {\r\n var row = $('#' + tableId).DataTable().row(this).data();\r\n VisitsDialog.ShowDialog(row.CompletedVisits);\r\n });\r\n $('#' + tableId).on('click', 'td.clickable-overdue', function () {\r\n var row = $('#' + tableId).DataTable().row(this).data();\r\n VisitsDialog.ShowDialog(row.OverdueVisits);\r\n });\r\n $('#' + tableId).on('click', 'td.clickable-notdone', function () {\r\n var row = $('#' + tableId).DataTable().row(this).data();\r\n VisitsDialog.ShowDialog(row.NotDoneVisits);\r\n });\r\n $('#' + tableId).on('click', 'td.clickable-discprior', function () {\r\n var row = $('#' + tableId).DataTable().row(this).data();\r\n VisitsDialog.ShowDialog(row.DiscPriorVisits);\r\n });\r\n });\r\n }\r\n\r\n private setCellPercentValue(row: any, cell: string, color: string, count: number): void {\r\n $(cell, row).css('background-color', color);\r\n $(cell, row).attr('title', 'n = ' + count);\r\n $(cell, row).attr('data-toggle', 'tooltip');\r\n $(cell, row).attr('data-placement', 'top');\r\n }\r\n\r\n exportTable(): void {\r\n\r\n let tableId: string = undefined;\r\n if (this.selectedGrouping == \"none\") { tableId = this.tableNoneId; }\r\n else if (this.selectedGrouping == \"byvisit\") { tableId = this.tableVisitId; }\r\n else if (this.selectedGrouping == \"bysite\") { tableId = this.tableSiteId; }\r\n if (tableId != undefined) {\r\n let table = $(\"#\" + this.tableNoneId).DataTable();\r\n (table).button(0).trigger(); \r\n }\r\n }\r\n\r\n private setCountFoundLabel(tableId: string, count: number): void {\r\n\r\n switch (count) {\r\n case 0: $('#' + tableId + '-found-count').text(\"No Records Found\"); break;\r\n case 1: $('#' + tableId + '-found-count').text(\"1 Record Found\"); break;\r\n default: $('#' + tableId + '-found-count').text(FormatNumberWithCommas(count + \"\") + \" Records Found\");\r\n }\r\n }\r\n\r\n private refresh(): void {\r\n //this.dataTable.columns.adjust();\r\n //this.tableColumnController.refresh();\r\n }\r\n\r\n private clearSearchBar(): void {\r\n //this.dataTable.search('').draw();\r\n }\r\n}","import { Task } from \"./task-tracker.entities\";\r\n\r\nvar Highcharts = require('highcharts/highcharts-gantt');\r\n\r\nexport class TaskTrackerGanttChart {\r\n\r\n chart: any;\r\n\r\n constructor() { }\r\n\r\n initialize(milestones: Task[], showStudyAreaPrefix: boolean): void {\r\n\r\n //console.log(milestones);\r\n\r\n let minDate: number = null;\r\n let maxDate: number = null;\r\n let startSpacerData: MilestoneDisplayData;\r\n let endSpacerData: MilestoneDisplayData;\r\n\r\n let chartHash: Map = new Map();\r\n let completionMap: Map = new Map();\r\n let taskCompletionMap: Map = new Map();\r\n let tempCategory = \"\";\r\n for (let i = 0; i < milestones.length; ++i) {\r\n let milestone = milestones[i];\r\n if (milestone.ActualStartDate != \"\" && milestone.ActualEndDate == \"\") {\r\n taskCompletionMap.set(milestone.Name, (milestone.ActualStartDate != \"\" && milestone.ActualEndDate == \"\"));\r\n }\r\n console.log(taskCompletionMap);\r\n if (showStudyAreaPrefix) {\r\n //milestone.Name = milestone.StudyArea + \" - \" + milestone.Name;\r\n milestone.GanttTaskCategory = milestone.StudyArea;\r\n }\r\n else { milestone.GanttTaskCategory = milestone.TaskCategory; }\r\n\r\n\r\n if (milestone.GanttTaskCategory != tempCategory && !completionMap.has(milestone.GanttTaskCategory)) {\r\n\r\n completionMap.set(milestone.GanttTaskCategory, true);\r\n tempCategory = milestone.GanttTaskCategory;\r\n }\r\n if (milestone.ActualEndDate == \"\" && (milestone.PlannedEndDate != \"\" || milestone.ActualStartDate != \"\")) {\r\n completionMap.set(milestone.GanttTaskCategory, false);\r\n }\r\n }\r\n\r\n let groupPrefix = \"group-\";\r\n\r\n for (let i = 0; i < milestones.length; ++i) {\r\n let milestone = milestones[i];\r\n \r\n //if (milestone.StartDate == \"\") milestone.DueDate = \"2021-06-01\";\r\n //if (milestone.DueDate == \"\") milestone.DueDate = \"2022-08-31\";\r\n\r\n //console.log(milestone.StartDate, milestone.DueDate, milestone.ActualStartDate, milestone.DeliveredDate);\r\n\r\n let categoryData: MilestoneDisplayData = chartHash.get(groupPrefix + milestone.GanttTaskCategory.toLowerCase());\r\n if (categoryData == null) {\r\n categoryData = new MilestoneDisplayData(milestone.GanttTaskCategory, \"group\", groupPrefix + milestone.GanttTaskCategory.toLowerCase(), null, null, null, (showStudyAreaPrefix) ? false : true);\r\n //categoryData = new MilestoneDisplayData(milestone.GanttTaskCategory, \"group\", milestone.SortOrder + \"\", null, null, (showStudyAreaPrefix) ? false : true);\r\n if (completionMap.get(milestone.GanttTaskCategory)) {\r\n categoryData.setColorByStatus(\"Completed\");\r\n }\r\n else {\r\n categoryData.setColorByStatus(\"Not Started\");\r\n }\r\n\r\n //the first category\r\n if (chartHash.size == 0) {\r\n startSpacerData = new MilestoneDisplayData(milestone.GanttTaskCategory, \"spacer\", null, null, null, null, null);\r\n endSpacerData = new MilestoneDisplayData(milestone.GanttTaskCategory, \"spacer\", null, null, null, null, null);\r\n }\r\n }\r\n\r\n categoryData.checkDates(this.getDate(milestone.PlannedEndDate), this.getDate(milestone.PlannedEndDate), this.getDate(milestone.ActualStartDate), (milestone.Status == \"Completed\") ? this.getDate(milestone.ActualEndDate) : (milestone.Status == \"In Progress\") ? this.today() : null);\r\n chartHash.set(groupPrefix + milestone.GanttTaskCategory.toLowerCase(), categoryData);\r\n\r\n //to set the bounds of the x-axis\r\n if (minDate == null || (categoryData.start != null && categoryData.start < minDate)) minDate = categoryData.start;\r\n if (maxDate == null || (categoryData.end != null && categoryData.end > maxDate)) maxDate = categoryData.end;\r\n\r\n let actualWork: MilestoneDisplayData = this.createActualWork(milestone, groupPrefix + milestone.GanttTaskCategory.toLowerCase(), this.today());\r\n //let plannedWork: MilestoneDisplayData = this.createPlannedWork(milestone, groupPrefix + milestone.GanttTaskCategory.toLowerCase());\r\n let actualMilestone: MilestoneDisplayData = this.createActualMilestone(milestone, groupPrefix + milestone.GanttTaskCategory.toLowerCase());\r\n let plannedMilestone: MilestoneDisplayData = this.createPlannedMilestone(milestone, groupPrefix + milestone.GanttTaskCategory.toLowerCase());\r\n\r\n //order matters\r\n if (actualWork != null) { chartHash.set(\"actualWork-\" + this.getMilestoneName(milestone).toLowerCase(), actualWork); }\r\n //if (plannedWork != null) { chartHash.set(\"plannedWork-\" + milestone.Name.toLowerCase(), plannedWork); }\r\n if (actualMilestone != null) { chartHash.set(\"actualDeliv-\" + this.getMilestoneName(milestone).toLowerCase(), actualMilestone); }\r\n if (plannedMilestone != null) { chartHash.set(\"plannedDeliv-\" + this.getMilestoneName(milestone).toLowerCase(), plannedMilestone); }\r\n }\r\n\r\n //to sort the hashed values\r\n let chartDataArray: MilestoneDisplayData[] = Array.from(chartHash.values());\r\n chartDataArray = chartDataArray.sort((a: MilestoneDisplayData, b: MilestoneDisplayData) => (a.sort < b.sort) ? -1 : 1);\r\n //console.log(chartDataArray);\r\n\r\n //push hidden data to end and beginning last so scroll sets to far left and not far right\r\n endSpacerData.start = maxDate + (30 * 24 * 60 * 60 * 1000); //30 days after latest event\r\n endSpacerData.end = endSpacerData.start;\r\n //chartHash.set(\"end-spacer\", endSpacerData);\r\n maxDate = endSpacerData.end;\r\n\r\n startSpacerData.start = minDate - (30 * 24 * 60 * 60 * 1000); //30 days before earliest event\r\n startSpacerData.end = startSpacerData.start;\r\n //chartHash.set(\"start-spacer\", startSpacerData);\r\n minDate = startSpacerData.start;\r\n\r\n chartDataArray.push(endSpacerData);\r\n chartDataArray.push(startSpacerData);\r\n //console.log(Array.from(chartHash.values()));\r\n //console.log(chartDataArray);\r\n\r\n // THE CHART\r\n Highcharts.ganttChart('gantt-chart', {\r\n credits: { enabled: false },\r\n chart: {\r\n styledMode: false,\r\n },\r\n xAxis: {\r\n min: minDate,\r\n max: maxDate,\r\n tickInterval: (24 * 3600 * 1000 * 30), //stay at month view\r\n currentDateIndicator: {\r\n width: 3,\r\n dashStyle: 'solid',\r\n color: 'rgba(39,73,110)',\r\n label: {\r\n format: ''\r\n }\r\n }\r\n },\r\n yAxis: {\r\n uniqueNames: true,\r\n staticScale: 30, //line height\r\n labels: {\r\n style: {\r\n width: '350px',\r\n wordwrap: 'break-word',\r\n fontSize: '12px',\r\n textOverflow: 'ellipsis'\r\n }\r\n }\r\n },\r\n tooltip: {\r\n headerFormat: '',\r\n dateTimeLabelFormats: {\r\n hour: \"%B %e, %Y\",\r\n },\r\n useHtml: true,\r\n formatter: function () {\r\n let tooltip: string = '' + this.key + '
';\r\n\r\n if (this.point.type == \"planned\") {\r\n tooltip += 'Planned Completion: ' + Highcharts.dateFormat('%b %d %Y', this.x2);\r\n }\r\n else {\r\n\r\n if (this.x != this.x2) {\r\n tooltip += '
Start: ' + Highcharts.dateFormat('%b %d %Y', this.x);\r\n if (this.point.type === \"group\") {\r\n if (!completionMap.get(this.key)) {\r\n tooltip += '
In Progress ';\r\n }\r\n else {\r\n tooltip += '
Completion: ' + Highcharts.dateFormat('%b %d %Y', this.x2);\r\n }\r\n }\r\n else {\r\n let name = this.point.name;\r\n if (name.includes(\"[\")) {\r\n name = name.substring(0, name.indexOf(\"[\")-1);\r\n }\r\n if (!taskCompletionMap.get(name)) {\r\n \r\n tooltip += '
Completed ';\r\n }\r\n else {\r\n tooltip += '
In Progress ';\r\n }\r\n }\r\n }\r\n else {\r\n tooltip += '
Completion: ' + Highcharts.dateFormat('%b %d %Y', this.x2);\r\n }\r\n }\r\n return tooltip;\r\n\r\n //+ 'Start: ' + Highcharts.dateFormat('%A, %b %d %Y', this.x) + '
' + 'Go live: ' + Highcharts.dateFormat('%A, %b %d %Y', this.x2)\r\n }\r\n },\r\n legend: { enabled: false },\r\n scrollbar: {\r\n enabled: true,\r\n },\r\n rangeSelector: {\r\n enabled: true,\r\n inputEnabled: false,\r\n selected: 3,\r\n buttonPosition: {\r\n align: 'right'\r\n },\r\n buttons: [{\r\n type: 'month',\r\n count: 3,\r\n text: '3m',\r\n events: { click: (event) => { this.setZoom(7862400000); } }\r\n }, {\r\n type: 'month',\r\n count: 6,\r\n text: '6m',\r\n events: { click: (event) => { this.setZoom(15897600000); } }\r\n }, {\r\n type: 'year',\r\n count: 1,\r\n text: '1y',\r\n events: { click: (event) => { this.setZoom(31536000000); } }\r\n }, {\r\n type: 'all',\r\n text: 'All',\r\n events: { click: (event) => { this.setZoom(-1); } }\r\n }]\r\n },\r\n\r\n series: [{ data: chartDataArray }]\r\n //series: [{ data: Array.from(chartHash.values()) }]\r\n\r\n }, (chart) => this.chart = chart);\r\n }\r\n\r\n today(): Date {\r\n let today: Date = new Date();\r\n\r\n // Set to 00:00:00:000 today\r\n today.setHours(24);\r\n today.setMinutes(0);\r\n today.setSeconds(0);\r\n today.setMilliseconds(0);\r\n return today;\r\n }\r\n\r\n createActualWork(milestone: Task, parent: string, today: Date): MilestoneDisplayData {\r\n\r\n let actual: MilestoneDisplayData = new MilestoneDisplayData(this.getMilestoneName(milestone), \"actual\", milestone.SortOrder + \"-actualWork\", parent, milestone.SortOrder, milestone.DependencyList, null);\r\n actual.checkDates(null, null, this.getDate(milestone.ActualStartDate), (milestone.Status == \"Completed\") ? this.getDate(milestone.ActualEndDate) : (milestone.Status == \"In Progress\") ? today : null);\r\n actual.setColorByStatus(\"Completed\"); //or in progress - doesn't matter - just need the non-planning color\r\n actual.milestone = false;\r\n\r\n if (actual.start != null && actual.end != null && actual.start != actual.end) return actual;\r\n else return null;\r\n }\r\n\r\n createActualMilestone(milestone: Task, parent: string): MilestoneDisplayData {\r\n\r\n if (milestone.ActualEndDate != null && milestone.ActualEndDate != \"\") {\r\n let actual: MilestoneDisplayData = new MilestoneDisplayData(this.getMilestoneName(milestone), \"actual\", milestone.SortOrder + \"actualMilestone\", parent, milestone.SortOrder, milestone.DependencyList, null);\r\n actual.setDates(this.getDate(milestone.ActualEndDate).getTime(), this.getDate(milestone.ActualEndDate).getTime());\r\n actual.setColorByStatus(\"Completed\"); //or in progress - doesn't matter - just need the non-planning color\r\n actual.milestone = true;\r\n return actual;\r\n }\r\n else return null;\r\n }\r\n\r\n //createPlannedWork(milestone: Task, parent: string): MilestoneDisplayData {\r\n // let planned: MilestoneDisplayData = new MilestoneDisplayData(milestone.Name, \"planned\", null, parent, null);\r\n // planned.checkDates(this.getDate(milestone.ActualStartDate), this.getDate(milestone.ActualEndDate), null, null);\r\n // planned.setColorByStatus(\"Not Started\");\r\n // planned.milestone = false;\r\n\r\n // if (planned.start != null && planned.end != null && planned.start != planned.end) return planned;\r\n // else return null;\r\n //}\r\n\r\n createPlannedMilestone(milestone: Task, parent: string): MilestoneDisplayData {\r\n\r\n if (milestone.PlannedEndDate != null && milestone.PlannedEndDate != \"\") {\r\n\r\n let planned: MilestoneDisplayData = new MilestoneDisplayData(this.getMilestoneName(milestone), \"planned\", milestone.SortOrder + \"-plannedMilestone\", parent, milestone.SortOrder, milestone.DependencyList, null);\r\n planned.setDates(this.getDate(milestone.PlannedEndDate).getTime(), this.getDate(milestone.PlannedEndDate).getTime());\r\n planned.setColorByStatus(\"Not Started\");\r\n planned.milestone = true;\r\n return planned;\r\n }\r\n else return null;\r\n }\r\n\r\n getMilestoneName(milestone: Task): string {\r\n if (milestone.VersionLabel && milestone.VersionLabel.length > 0) return milestone.Name + \" [\" + milestone.VersionLabel + \"]\";\r\n else return milestone.Name;\r\n }\r\n\r\n setZoom(delta: number): void {\r\n let start: number = this.chart.xAxis[0].dataMin;\r\n let end: number = (delta == -1) ? this.chart.xAxis[0].dataMax : start + delta\r\n\r\n this.chart.xAxis[0].setExtremes(start, end);\r\n this.chart.redraw();\r\n }\r\n\r\n getDate(dateString: string): Date {\r\n if (dateString == null || dateString == \"\") return null;\r\n\r\n let tempDate: Date = new Date(dateString);\r\n tempDate.setHours(12);\r\n tempDate.setMinutes(0);\r\n tempDate.setSeconds(0);\r\n tempDate.setMilliseconds(0);\r\n\r\n return tempDate;\r\n }\r\n\r\n}\r\n\r\nclass MilestoneDisplayData {\r\n\r\n name: string = null;\r\n id: string = null;\r\n parent: string = null;\r\n dependency: string[] = null;\r\n start: number = null;\r\n end: number = null;\r\n sort: number = null;\r\n color: string = null;\r\n type: string = null;\r\n milestone: boolean = null;\r\n collapsed: boolean = null;\r\n pointWidth: number = 20; //default\r\n\r\n constructor(name: string, type: string, id: string, parent: string, sort: number, dependency: string[], collapsed: boolean) {\r\n this.name = name;\r\n this.type = type;\r\n this.id = id;\r\n this.parent = parent;\r\n this.sort = sort;\r\n this.dependency = dependency;\r\n this.collapsed = collapsed;\r\n }\r\n\r\n //once a milestone is made active, don't let it revert to scheduled\r\n setColorByStatus(status: string) {\r\n if (status == 'Completed' || status == 'In Progress') this.color = 'rgba(21,40,80)';\r\n else if (this.color == null) {\r\n this.color = 'rgba(255, 199, 87)';\r\n this.pointWidth = 10;\r\n }\r\n }\r\n\r\n setDates(start: number, end: number): void {\r\n this.start = start;\r\n this.end = end;\r\n }\r\n\r\n checkDates(plannedStart: Date, plannedEnd: Date, actualStart: Date, actualEnd: Date): void {\r\n this.checkDateBounds(plannedStart);\r\n this.checkDateBounds(plannedEnd);\r\n this.checkDateBounds(actualStart);\r\n this.checkDateBounds(actualEnd);\r\n }\r\n\r\n checkDateBounds(date: Date): void {\r\n if (date != null) {\r\n if (this.start == null) this.start = date.getTime();\r\n else if (date.getTime() < this.start) this.start = date.getTime();\r\n\r\n if (this.end == null) this.end = date.getTime();\r\n else if (date.getTime() > this.end) this.end = date.getTime();\r\n }\r\n }\r\n}\r\n"],"sourceRoot":""}