principal-dashboard-lib-v2 0.0.36 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/bundles/principal-dashboard-lib-v2.umd.js +1238 -84
  2. package/bundles/principal-dashboard-lib-v2.umd.js.map +1 -1
  3. package/bundles/principal-dashboard-lib-v2.umd.min.js +1 -1
  4. package/bundles/principal-dashboard-lib-v2.umd.min.js.map +1 -1
  5. package/esm2015/lib/chartheader/chartheader.component.js +18 -4
  6. package/esm2015/lib/chartview/chartview.component.js +114 -9
  7. package/esm2015/lib/full-table-view/full-table-view.component.js +119 -3
  8. package/esm2015/lib/insights/insights.component.js +519 -0
  9. package/esm2015/lib/principal-dashboard-lib-v2.component.js +10 -4
  10. package/esm2015/lib/principal-dashboard-lib-v2.module.js +6 -2
  11. package/esm2015/lib/principal-dashboard-lib-v2.service.js +23 -12
  12. package/esm2015/lib/small-table-view/small-table-view.component.js +172 -54
  13. package/esm2015/lib/timeline/timeline.component.js +195 -0
  14. package/esm2015/principal-dashboard-lib-v2.js +3 -1
  15. package/fesm2015/principal-dashboard-lib-v2.js +1164 -82
  16. package/fesm2015/principal-dashboard-lib-v2.js.map +1 -1
  17. package/lib/chartheader/chartheader.component.d.ts +3 -0
  18. package/lib/chartview/chartview.component.d.ts +20 -0
  19. package/lib/full-table-view/full-table-view.component.d.ts +10 -0
  20. package/lib/insights/insights.component.d.ts +100 -0
  21. package/lib/principal-dashboard-lib-v2.component.d.ts +4 -1
  22. package/lib/principal-dashboard-lib-v2.service.d.ts +14 -0
  23. package/lib/small-table-view/small-table-view.component.d.ts +23 -0
  24. package/lib/timeline/timeline.component.d.ts +76 -0
  25. package/package.json +1 -1
  26. package/principal-dashboard-lib-v2.d.ts +2 -0
  27. package/principal-dashboard-lib-v2.metadata.json +1 -1
@@ -1,8 +1,12 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('angular-google-charts'), require('file-saver'), require('xlsx'), require('@angular/common'), require('ng2-datepicker')) :
3
- typeof define === 'function' && define.amd ? define('principal-dashboard-lib-v2', ['exports', '@angular/core', 'rxjs', 'angular-google-charts', 'file-saver', 'xlsx', '@angular/common', 'ng2-datepicker'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['principal-dashboard-lib-v2'] = {}, global.ng.core, global.rxjs, global.angularGoogleCharts, global.FileSaver, global.XLSX, global.ng.common, global.ng2Datepicker));
5
- }(this, (function (exports, i0, rxjs, angularGoogleCharts, FileSaver, XLSX, common, ng2Datepicker) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('angular-google-charts'), require('file-saver'), require('xlsx'), require('@angular/common'), require('ng2-datepicker'), require('html2canvas'), require('jspdf')) :
3
+ typeof define === 'function' && define.amd ? define('principal-dashboard-lib-v2', ['exports', '@angular/core', 'rxjs', 'angular-google-charts', 'file-saver', 'xlsx', '@angular/common', 'ng2-datepicker', 'html2canvas', 'jspdf'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['principal-dashboard-lib-v2'] = {}, global.ng.core, global.rxjs, global.angularGoogleCharts, global.FileSaver, global.XLSX, global.ng.common, global.ng2Datepicker, global.html2canvas, global.jspdf));
5
+ }(this, (function (exports, i0, rxjs, angularGoogleCharts, FileSaver, XLSX, common, ng2Datepicker, html2canvas, jspdf) { 'use strict';
6
+
7
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
+
9
+ var html2canvas__default = /*#__PURE__*/_interopDefaultLegacy(html2canvas);
6
10
 
7
11
  var PrincipalDashboardLibV2Service = /** @class */ (function () {
8
12
  function PrincipalDashboardLibV2Service() {
@@ -29,7 +33,14 @@
29
33
  //for get details of selected user
30
34
  this.detailedviewData = new rxjs.BehaviorSubject(this.userMetadata);
31
35
  this.detailedviewData$ = this.detailedviewData.asObservable();
36
+ // true while the Insights tab is selected instead of the user / classroom views
37
+ this.isInsightsView = new rxjs.BehaviorSubject(false);
38
+ this.isInsightsView$ = this.isInsightsView.asObservable();
32
39
  this.openSmallTable = new rxjs.BehaviorSubject(false);
40
+ // colour of the timeline bar that opened the details popup
41
+ this.detailAccentColor = '';
42
+ // set when the popup should list every session of a timeline row instead of one session
43
+ this.detailRowRequest = null;
33
44
  this.openSmallTable$ = this.openSmallTable.asObservable();
34
45
  }
35
46
  PrincipalDashboardLibV2Service.prototype.genarateResourseReqiuredData = function (data) {
@@ -422,10 +433,7 @@
422
433
  for (var i = 0; i < this.userConsoleData.length; i++) {
423
434
  //console.log('bbb-->',this.userConsoleData[i].terminalLocation)
424
435
  var roomName = this.userConsoleData[i].terminalLocation;
425
- if (roomName === "") {
426
- //roomName = "No Name"
427
- }
428
- var location = roomName + " - " + this.userConsoleData[i].terminalIP;
436
+ var location = this.getRoomLabel(roomName, this.userConsoleData[i].terminalIP);
429
437
  //console.log('bbblocation-->',location)
430
438
  var userData = [location, this.userConsoleData[i].startTime, this.userConsoleData[i].endTime];
431
439
  dataTableforChart.push(userData);
@@ -451,6 +459,11 @@
451
459
  dataTableforChart = this.uniq(dataTableforChart);
452
460
  return dataTableforChart;
453
461
  };
462
+ // Row label for classroom wise chart: room name, or IP as fallback when name is missing
463
+ PrincipalDashboardLibV2Service.prototype.getRoomLabel = function (roomName, roomIP) {
464
+ var name = (roomName === undefined || roomName === null || roomName === 'undefined') ? '' : String(roomName).trim();
465
+ return name !== '' ? name : (roomIP || '');
466
+ };
454
467
  PrincipalDashboardLibV2Service.prototype.uniq = function (a) {
455
468
  return Array.from(new Set(a));
456
469
  };
@@ -551,29 +564,31 @@
551
564
  //console.log("bbb allRoom-->",this.userConsoleData[i])
552
565
  var roomName = this.userConsoleData[i].terminalLocation;
553
566
  var roomIP = this.userConsoleData[i].terminalIP;
554
- if (roomName === "" || roomName === undefined) {
555
- roomName = "No Name";
556
- }
557
- var location = roomName + " - " + roomIP;
567
+ var location = this.getRoomLabel(roomName, roomIP);
558
568
  //console.log('bbbbRoom',location)
559
569
  // let userData = [this.userConsoleData[i].terminalIP,this.userConsoleData[i].startTime,this.userConsoleData[i].endTime];
560
570
  var userData = [location, this.userConsoleData[i].startTime, this.userConsoleData[i].endTime];
561
571
  dataTableforChart.push(userData);
562
572
  }
563
573
  var inactiveRoonName = void 0;
574
+ // rooms already used today are in the chart above, add only the idle ones
575
+ var activeRoomIPs = this.userConsoleData.map(function (room) { return room.terminalIP; });
564
576
  for (var i = 0; i < this.schoolTerminalData.length; i++) {
565
577
  //console.log("schoolTerminalData",this.schoolTerminalData);
566
- inActiveClassRoom = this.schoolTerminalData[i].location + ' - ' + this.schoolTerminalData[i].terminalKey;
578
+ if (activeRoomIPs.includes(this.schoolTerminalData[i].terminalKey)) {
579
+ continue;
580
+ }
581
+ inActiveClassRoom = this.getRoomLabel(this.schoolTerminalData[i].location, this.schoolTerminalData[i].terminalKey);
567
582
  //inactiveRoonName = this.schoolTerminalData[i].location;
568
583
  myChartRowData = {
569
584
  userId: "",
570
585
  userName: "",
571
586
  startTime: new Date(this.myEndTime),
572
587
  endTime: new Date(this.myEndTime),
573
- terminalIP: inActiveClassRoom,
588
+ terminalIP: this.schoolTerminalData[i].terminalKey,
574
589
  resourceMetauserConsoleData: [],
575
590
  duration: 0,
576
- terminalLocation: "",
591
+ terminalLocation: this.schoolTerminalData[i].location,
577
592
  userSubjects: ""
578
593
  };
579
594
  allRoomsDetails.push(myChartRowData);
@@ -842,9 +857,11 @@
842
857
  PrincipalDashboardLibV2Service.ctorParameters = function () { return []; };
843
858
 
844
859
  var PrincipalDashboardLibV2Component = /** @class */ (function () {
845
- function PrincipalDashboardLibV2Component() {
860
+ function PrincipalDashboardLibV2Component(pdService) {
861
+ this.pdService = pdService;
846
862
  this.userSelectedDate = new i0.EventEmitter();
847
863
  this.messageForUser = "Loding...";
864
+ this.isInsights = false;
848
865
  }
849
866
  Object.defineProperty(PrincipalDashboardLibV2Component.prototype, "responseStatus", {
850
867
  set: function (res) {
@@ -855,6 +872,8 @@
855
872
  configurable: true
856
873
  });
857
874
  PrincipalDashboardLibV2Component.prototype.ngOnInit = function () {
875
+ var _this = this;
876
+ this.pdService.isInsightsView.subscribe(function (value) { return _this.isInsights = value; });
858
877
  // console.log(" IN --> 7 data",this.data);
859
878
  };
860
879
  PrincipalDashboardLibV2Component.prototype.setMessageForUser = function (res) {
@@ -870,11 +889,13 @@
870
889
  PrincipalDashboardLibV2Component.decorators = [
871
890
  { type: i0.Component, args: [{
872
891
  selector: 'lib-principal-dashboard-lib-v2',
873
- template: "<div id=\"reprtModule\">\n <div id=\"pd-mainDiv\" class=\"pd-mainContainer\">\n <lib-header [data]=\"data\" (userSelectedDate)=\"onUserSelectDate($event)\">\n </lib-header>\n <lib-chartheader></lib-chartheader>\n \n <div id=\"pd-preloader\" class=\"pd-preloaderDiv\" *ngIf=\"responseStatus !== 'true' && !data\">\n {{messageForUser}} \n </div>\n <lib-chartview *ngIf=\"data\" [data]=\"data\"></lib-chartview>\n <!-- <lib-tableview *ngIf=\"data\" [data]=\"data\"></lib-tableview> -->\n <lib-full-table-view *ngIf=\"data\" [data]=\"data\"></lib-full-table-view>\n <!-- <lib-detailedview *ngIf=\"data\" [data]=\"data\"></lib-detailedview> -->\n <lib-small-table-view *ngIf=\"data\" [data]=\"data\"></lib-small-table-view>\n \n </div>\n</div>\n ",
892
+ template: "<div id=\"reprtModule\">\n <div id=\"pd-mainDiv\" class=\"pd-mainContainer\">\n <lib-header [data]=\"data\" (userSelectedDate)=\"onUserSelectDate($event)\">\n </lib-header>\n <lib-chartheader></lib-chartheader>\n \n <div id=\"pd-preloader\" class=\"pd-preloaderDiv\" *ngIf=\"responseStatus !== 'true' && !data\">\n {{messageForUser}} \n </div>\n <!-- kept alive (only hidden) while on Insights so the timeline keeps its state -->\n <div [style.display]=\"isInsights ? 'none' : 'block'\">\n <lib-chartview *ngIf=\"data\" [data]=\"data\"></lib-chartview>\n <!-- <lib-tableview *ngIf=\"data\" [data]=\"data\"></lib-tableview> -->\n <lib-full-table-view *ngIf=\"data\" [data]=\"data\"></lib-full-table-view>\n </div>\n <lib-insights *ngIf=\"data && isInsights\" [data]=\"data\"></lib-insights>\n <!-- <lib-detailedview *ngIf=\"data\" [data]=\"data\"></lib-detailedview> -->\n <lib-small-table-view *ngIf=\"data\" [data]=\"data\"></lib-small-table-view>\n \n </div>\n</div>\n ",
874
893
  styles: [".pd-mainContainer{width:100%;height:100%}.pd-mainContainer .pd-preloaderDiv{text-align:center;line-height:27;position:absolute;width:100%;height:87%;z-index:2}.bs-datepicker-head{background-color:#000!important}"]
875
894
  },] }
876
895
  ];
877
- PrincipalDashboardLibV2Component.ctorParameters = function () { return []; };
896
+ PrincipalDashboardLibV2Component.ctorParameters = function () { return [
897
+ { type: PrincipalDashboardLibV2Service }
898
+ ]; };
878
899
  PrincipalDashboardLibV2Component.propDecorators = {
879
900
  data: [{ type: i0.Input }],
880
901
  responseStatus: [{ type: i0.Input }],
@@ -889,9 +910,10 @@
889
910
  this.datadisplaytype = 'UserWise';
890
911
  this.isUserWise = true;
891
912
  this.isChartView = true;
913
+ this.isInsights = false;
892
914
  }
893
915
  ChartheaderComponent.prototype.ngOnInit = function () {
894
- this.libVersion = "0.0.36";
916
+ this.libVersion = "0.0.37";
895
917
  // this.myIcon = document.getElementById('myIconImg');
896
918
  // this.changeChatType()
897
919
  };
@@ -907,21 +929,34 @@
907
929
  this.pdService.chartType.next(this.mytype);
908
930
  }
909
931
  };
932
+ ChartheaderComponent.prototype.showInsights = function () {
933
+ this.isInsights = true;
934
+ this.pdService.openSmallTable.next(false);
935
+ this.pdService.isInsightsView.next(true);
936
+ };
910
937
  ChartheaderComponent.prototype.changeDataTypeClassRoom = function () {
938
+ this.leaveInsights();
911
939
  this.isUserWise = false;
912
940
  this.pdService.isUserWise_data.next(false);
913
941
  };
914
942
  ChartheaderComponent.prototype.changeDataTypeUser = function () {
943
+ this.leaveInsights();
915
944
  this.isUserWise = true;
916
945
  this.pdService.isUserWise_data.next(true);
917
946
  };
947
+ ChartheaderComponent.prototype.leaveInsights = function () {
948
+ if (this.isInsights) {
949
+ this.isInsights = false;
950
+ this.pdService.isInsightsView.next(false);
951
+ }
952
+ };
918
953
  return ChartheaderComponent;
919
954
  }());
920
955
  ChartheaderComponent.decorators = [
921
956
  { type: i0.Component, args: [{
922
957
  selector: 'lib-chartheader',
923
- template: "\n<div id=\"pd-chartheader\">\n <div id=\"pd-chatTypeBtnDiv\" class=\"pd-tabIconBtn\" [ngClass]=\" isChartView ? 'tableIconImg' : 'chartIconImg'\" (click)=\"changeChatType()\">\n </div>\n \n <div id=\"pd-userWisechatTypeBtn\" class=\"userWiseBtn\" [ngStyle]=\"{ color: isUserWise ? '#ffffff' : '#000000' , backgroundColor: isUserWise ? '#000000' : '#c4c4c4'}\"\n (click)=\"changeDataTypeUser()\">User wise</div>\n <div id=\"pd-classRoomWiseBtn\" class=\"classWiseBtn\" [ngStyle]=\"{ color: isUserWise ? '#000000' : '#ffffff' , backgroundColor: isUserWise ? '#c4c4c4' : '#000000'}\"\n (click)=\"changeDataTypeClassRoom()\">Classroom wise</div>\n\n \n <div class=\"libVersionDiv\">v{{libVersion}}</div>\n</div>",
924
- styles: ["#pd-chartheader{font-size:12pt;font-weight:400;color:#5c5c5c;z-index:1;width:100%;height:40px;position:relative;margin:10 auto;text-align:center;background-color:#e0e0e0}#pd-chartheader #pd-classRoomWiseBtn,#pd-chartheader #pd-userWisechatTypeBtn{background:-moz-linear-gradient(center top,#45a1da 5%,#1172ae 100%);display:inline-block;padding:0 20px;cursor:pointer;margin:5px 0 0;width:160px;height:30px;line-height:30px}#pd-chartheader .libVersionDiv{position:absolute;font-size:12px;color:#343a40;margin-top:-21px;font-weight:700;margin-left:71px}.userWiseBtn{border-radius:6px 0 0 6px}.classWiseBtn{border-radius:0 6px 6px 0}.pd-tabIconBtn{cursor:pointer;position:absolute;left:10px;top:-17px;width:62px;height:52px;background-repeat:no-repeat}.tableIconImg{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAAAzCAYAAADPX7uaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkZDQzc3REFDMkQ2MTFFMkIzNDhCNTE1RDg3OUE1QTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkZDQzc3REJDMkQ2MTFFMkIzNDhCNTE1RDg3OUE1QTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRkNDNzdEOEMyRDYxMUUyQjM0OEI1MTVEODc5QTVBNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRkNDNzdEOUMyRDYxMUUyQjM0OEI1MTVEODc5QTVBNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnXBGHQAAAblSURBVHja3FrLbxtF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\")}.chartIconImg{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAAAzCAYAAADPX7uaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDk0MUYxOEJDQzFFMTFFMkIxOUNERTUyRTI5MjFDNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDk0MUYxOENDQzFFMTFFMkIxOUNERTUyRTI5MjFDNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEOTQxRjE4OUNDMUUxMUUyQjE5Q0RFNTJFMjkyMUM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEOTQxRjE4QUNDMUUxMUUyQjE5Q0RFNTJFMjkyMUM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpdyrHsAAAZISURBVHja3FpLbBtFGP5nd/1I/CCOm0AqUqRCKwQHJB6qgIioVBUnEkCV4MAhUgsHuCU8Du0hCXAAoRwAwYEeQJW4AAcCiuBSiebI4wDiIR4qEKQooYkdx3b83OGftXczO96x1/HGWXeiiXd2Znf/b77/NbNLwF0h0FuFthqguQRKmkwA8RE46nZsMyDE4Zj4UAuocEy5c+KxlHEnoIrQJi004CBAU4eqC+MID96JQUUAzVf+vJ9UXRcA81WcCCpjnAesCr+KgwYcNONOgKvCry4yrjmoNuHAqvUxmjAJfgTOg63Uq6jqVtuJcZ7tQHL01tgd4xMvBEJ9E0RRRn0dw3R9pVzcWfz568U3N1b+3JbYf4NzE0Frh44ci995cvKNM4+Mn3n0oXvhxuSAr4P32kZ69PMr3z7/iare9MuVL15av/prWuL4HD22pebH7z/9lBYMP3H2sVO+B80Kk5HJijI/ftuJU09y5tqAU2nm2EKRG6befvmcMYZS2hOVFSZzqD8+JThlm0/SJPHbAI42PTwYj1o37JWSQJlR9iEHtk2MVHNgnGed6txMshKLxXw5EdlsdtfJ7conDb+yOG4lKVQAHo1GYXZ21leg5+fnbTIKwB1Dr9YiXQVd141qlmq1avxOT0/7AvTCwgJUKhWbjNyxNMXWWsZGgXF+AlZXV10J9/HYFFS38xBAAnAa4cH3zsPhB+7qGPTIyEgNDSEyxttelvLIO7bp0rU0DOd0OEbisAFlqJRK3icvvIxeABdVXSyKorTexVBVI5sk9T92jZvrWsklazeTt2NVZ0VFQEzN3BcEjeOJQjoGLj7Xc1UXgfPHTHhXwAnnYikTunPGRZk8B65Tuaq7ZdxyqZQw1AZo1VB/74DbVJ16our2h/BAeeDr2Txm/7vjIsEgRIMB+64BqU2Cug/A7W3w3rnxKsoLf987lyDDHhgI4V1VePGe2+HC+Am7I6TMxhVPbFy0d8+dmyyBEYEHNLxV/BAukQ4DGRjAmJ3i+ond1hW1Y8ZF9rvu1W2GjA8kODFErwDRGtVZoYrh1U1V38ptwoeX37Jtkpw9PQORcKxj1e8cuN7cuYkOjBoJYi1sWf2kBoyP46wvW8zAp99chEACIDmYgNQ/OXj65HMQV9tf+/My0v1m3NlOCesw7M7st7y64dzY+VqfpgWMHQGtHxc/yTBsrxVwQrQ92X9Xw5ldQM6OERgP3BYhMJCbcVypRwRaZSzVtN3M6kr5ImTW07ZnDR4ZcpWteRTO5IxLY7jJNLFv29P6HztvVuC0gdY1gp3/6avv4KNn34UgihgO90GOFOD1lQ+6yLhOXXlJGwLirKo6YWszKs9yuHkiGBKjpA+SJAY3D90CPxR+d23jTOZ9X6Q4AlBA/kqPSE6Txi6mHVVU2yqtQLMXoPsSx+lelqWkUU4j2uFJJ8YJNMWF86gcgHMTGG+ZeFTxocUyQLCRWubYKDizQZtgJ9YOUhcTmPY3ImgNfJvgZJ2mGXR9I6KtJR+mogTjMGHpqzAOe4ytJ1VEwWxbZZcq0NiFSRBeE1b78T85WFU3c3VR5a9lcwY8SstA0+uQPTps9e2ktzFz1+A3yEBer8JRZgqGVeC98bCUwtVddQuK+RJeX3tWpVCGAi3BJl7zR/oqpLdS0sxxL6rObyWbb0WDWHGJBeGHz53//tVnJhORcMi6YGxszHfby3Nzc7C8vGy1c4UiXHj/s9Tli6/djc0C1iLb+oPdt6h626oeiURgZmbGV8ATiYQnqm57qygmMEtLS+DHIklgqMxtahK/an06QT3YXu524eTVwflDICnj+nUEXHfLuD4xMfEfO5EFSPUycMTxL/tdXFyMyxinu9fQzOTk5A4e95nJQLlShR5Dzrd2GCbM5/t4rJqDivOl8MqlL4udyjEYjxrZR6lcMh4aVJVau1JP8ahutIOhmmybmawXKlYQ/Z/IOM92yloXYImu/zjiBQHFNaEtG2eGTG94H+GxMGzIuqXyIuNm+RtrkiUxAODFPrCZb4ZdsuQF43r9fhtYj4v3tb0Tx1nJ17O267EUkfF+kXFzJv7ymGmz9Lc5Pu9lbsMxb3l2p29ZZV8s++XD3WYLWwDnD3sb+mXfpUOPgHU7CSCz8VZOqVeL1En+L8AAjJTPo0iRABoAAAAASUVORK5CYII=\")}"]
958
+ template: "\n<div id=\"pd-chartheader\">\n <div id=\"pd-chatTypeBtnDiv\" *ngIf=\"!isInsights\" class=\"pd-tabIconBtn\" [ngClass]=\" isChartView ? 'tableIconImg' : 'chartIconImg'\" (click)=\"changeChatType()\">\n </div>\n \n <div id=\"pd-userWisechatTypeBtn\" class=\"userWiseBtn\" [ngStyle]=\"{ color: isUserWise && !isInsights ? '#ffffff' : '#000000' , backgroundColor: isUserWise && !isInsights ? '#000000' : '#c4c4c4'}\"\n (click)=\"changeDataTypeUser()\">User wise</div>\n <div id=\"pd-classRoomWiseBtn\" class=\"classWiseBtn\" [ngStyle]=\"{ color: !isUserWise && !isInsights ? '#ffffff' : '#000000' , backgroundColor: !isUserWise && !isInsights ? '#000000' : '#c4c4c4'}\"\n (click)=\"changeDataTypeClassRoom()\">Classroom wise</div><div id=\"pd-insightsBtn\" class=\"insightsBtn\" [ngStyle]=\"{ color: isInsights ? '#ffffff' : '#000000' , backgroundColor: isInsights ? '#000000' : '#c4c4c4'}\"\n (click)=\"showInsights()\">Insights</div>\n\n \n <div class=\"libVersionDiv\">v{{libVersion}}</div>\n</div>",
959
+ styles: ["#pd-chartheader{font-size:12pt;font-weight:400;color:#5c5c5c;z-index:1;width:100%;height:40px;position:relative;margin:10 auto;text-align:center;background-color:#e0e0e0}#pd-chartheader #pd-classRoomWiseBtn,#pd-chartheader #pd-insightsBtn,#pd-chartheader #pd-userWisechatTypeBtn{background:-moz-linear-gradient(center top,#45a1da 5%,#1172ae 100%);display:inline-block;padding:0 20px;cursor:pointer;margin:5px 0 0;width:160px;height:30px;line-height:30px}#pd-chartheader .libVersionDiv{position:absolute;font-size:12px;color:#343a40;margin-top:-21px;font-weight:700;margin-left:71px}.userWiseBtn{border-radius:6px 0 0 6px}.insightsBtn{border-radius:0 6px 6px 0;border-left:1px solid #e0e0e0}.pd-tabIconBtn{cursor:pointer;position:absolute;left:10px;top:-17px;width:62px;height:52px;background-repeat:no-repeat}.tableIconImg{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAAAzCAYAAADPX7uaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkZDQzc3REFDMkQ2MTFFMkIzNDhCNTE1RDg3OUE1QTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkZDQzc3REJDMkQ2MTFFMkIzNDhCNTE1RDg3OUE1QTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRkNDNzdEOEMyRDYxMUUyQjM0OEI1MTVEODc5QTVBNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRkNDNzdEOUMyRDYxMUUyQjM0OEI1MTVEODc5QTVBNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnXBGHQAAAblSURBVHja3FrLbxtF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\")}.chartIconImg{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAAAzCAYAAADPX7uaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDk0MUYxOEJDQzFFMTFFMkIxOUNERTUyRTI5MjFDNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDk0MUYxOENDQzFFMTFFMkIxOUNERTUyRTI5MjFDNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEOTQxRjE4OUNDMUUxMUUyQjE5Q0RFNTJFMjkyMUM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEOTQxRjE4QUNDMUUxMUUyQjE5Q0RFNTJFMjkyMUM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpdyrHsAAAZISURBVHja3FpLbBtFGP5nd/1I/CCOm0AqUqRCKwQHJB6qgIioVBUnEkCV4MAhUgsHuCU8Du0hCXAAoRwAwYEeQJW4AAcCiuBSiebI4wDiIR4qEKQooYkdx3b83OGftXczO96x1/HGWXeiiXd2Znf/b77/NbNLwF0h0FuFthqguQRKmkwA8RE46nZsMyDE4Zj4UAuocEy5c+KxlHEnoIrQJi004CBAU4eqC+MID96JQUUAzVf+vJ9UXRcA81WcCCpjnAesCr+KgwYcNONOgKvCry4yrjmoNuHAqvUxmjAJfgTOg63Uq6jqVtuJcZ7tQHL01tgd4xMvBEJ9E0RRRn0dw3R9pVzcWfz568U3N1b+3JbYf4NzE0Frh44ci995cvKNM4+Mn3n0oXvhxuSAr4P32kZ69PMr3z7/iare9MuVL15av/prWuL4HD22pebH7z/9lBYMP3H2sVO+B80Kk5HJijI/ftuJU09y5tqAU2nm2EKRG6befvmcMYZS2hOVFSZzqD8+JThlm0/SJPHbAI42PTwYj1o37JWSQJlR9iEHtk2MVHNgnGed6txMshKLxXw5EdlsdtfJ7conDb+yOG4lKVQAHo1GYXZ21leg5+fnbTIKwB1Dr9YiXQVd141qlmq1avxOT0/7AvTCwgJUKhWbjNyxNMXWWsZGgXF+AlZXV10J9/HYFFS38xBAAnAa4cH3zsPhB+7qGPTIyEgNDSEyxttelvLIO7bp0rU0DOd0OEbisAFlqJRK3icvvIxeABdVXSyKorTexVBVI5sk9T92jZvrWsklazeTt2NVZ0VFQEzN3BcEjeOJQjoGLj7Xc1UXgfPHTHhXwAnnYikTunPGRZk8B65Tuaq7ZdxyqZQw1AZo1VB/74DbVJ16our2h/BAeeDr2Txm/7vjIsEgRIMB+64BqU2Cug/A7W3w3rnxKsoLf987lyDDHhgI4V1VePGe2+HC+Am7I6TMxhVPbFy0d8+dmyyBEYEHNLxV/BAukQ4DGRjAmJ3i+ond1hW1Y8ZF9rvu1W2GjA8kODFErwDRGtVZoYrh1U1V38ptwoeX37Jtkpw9PQORcKxj1e8cuN7cuYkOjBoJYi1sWf2kBoyP46wvW8zAp99chEACIDmYgNQ/OXj65HMQV9tf+/My0v1m3NlOCesw7M7st7y64dzY+VqfpgWMHQGtHxc/yTBsrxVwQrQ92X9Xw5ldQM6OERgP3BYhMJCbcVypRwRaZSzVtN3M6kr5ImTW07ZnDR4ZcpWteRTO5IxLY7jJNLFv29P6HztvVuC0gdY1gp3/6avv4KNn34UgihgO90GOFOD1lQ+6yLhOXXlJGwLirKo6YWszKs9yuHkiGBKjpA+SJAY3D90CPxR+d23jTOZ9X6Q4AlBA/kqPSE6Txi6mHVVU2yqtQLMXoPsSx+lelqWkUU4j2uFJJ8YJNMWF86gcgHMTGG+ZeFTxocUyQLCRWubYKDizQZtgJ9YOUhcTmPY3ImgNfJvgZJ2mGXR9I6KtJR+mogTjMGHpqzAOe4ytJ1VEwWxbZZcq0NiFSRBeE1b78T85WFU3c3VR5a9lcwY8SstA0+uQPTps9e2ktzFz1+A3yEBer8JRZgqGVeC98bCUwtVddQuK+RJeX3tWpVCGAi3BJl7zR/oqpLdS0sxxL6rObyWbb0WDWHGJBeGHz53//tVnJhORcMi6YGxszHfby3Nzc7C8vGy1c4UiXHj/s9Tli6/djc0C1iLb+oPdt6h626oeiURgZmbGV8ATiYQnqm57qygmMEtLS+DHIklgqMxtahK/an06QT3YXu524eTVwflDICnj+nUEXHfLuD4xMfEfO5EFSPUycMTxL/tdXFyMyxinu9fQzOTk5A4e95nJQLlShR5Dzrd2GCbM5/t4rJqDivOl8MqlL4udyjEYjxrZR6lcMh4aVJVau1JP8ahutIOhmmybmawXKlYQ/Z/IOM92yloXYImu/zjiBQHFNaEtG2eGTG94H+GxMGzIuqXyIuNm+RtrkiUxAODFPrCZb4ZdsuQF43r9fhtYj4v3tb0Tx1nJ17O267EUkfF+kXFzJv7ymGmz9Lc5Pu9lbsMxb3l2p29ZZV8s++XD3WYLWwDnD3sb+mXfpUOPgHU7CSCz8VZOqVeL1En+L8AAjJTPo0iRABoAAAAASUVORK5CYII=\")}"]
925
960
  },] }
926
961
  ];
927
962
  ChartheaderComponent.ctorParameters = function () { return [
@@ -1259,6 +1294,12 @@
1259
1294
  this.allList = "All Users";
1260
1295
  this.isAllUsers = false;
1261
1296
  this.isUserWise = false;
1297
+ this.isSubscribed = false;
1298
+ // set to false to go back to the Google timeline chart
1299
+ this.useCustomTimeline = true;
1300
+ this.isTableView = false;
1301
+ this.timelineRows = [];
1302
+ this.timelineList = [];
1262
1303
  this.myChartDataMatrix = new Array();
1263
1304
  this.totalChartDataMatrix = new Array();
1264
1305
  }
@@ -1284,6 +1325,7 @@
1284
1325
  this.currentList = "Current Users";
1285
1326
  this.allList = "All Users";
1286
1327
  this.isAllUsers = false;
1328
+ this.pdService.isAllUsers.next(false);
1287
1329
  this.isUserWise = false;
1288
1330
  this.myChartDataMatrix = [];
1289
1331
  this.totalChartDataMatrix = [];
@@ -1296,6 +1338,7 @@
1296
1338
  if (this.myXmlData) {
1297
1339
  this.currentdataList = this.pdService.genarateUserReqiuredData(this.myXmlData);
1298
1340
  this.alluserList = this.pdService.allUserChartData();
1341
+ this.allRoomsList = this.pdService.allRoomsData();
1299
1342
  this.resourceDataList = this.pdService.genarateResourseReqiuredData(this.myXmlData);
1300
1343
  this.totalUsers = this.currentdataList.usersMetadata.length;
1301
1344
  this.eligibleUsers = this.findEligibleUserCount();
@@ -1303,6 +1346,12 @@
1303
1346
  this.currentRooms = this.findCurrentClassrooms();
1304
1347
  this.findCurrentUserCount();
1305
1348
  }
1349
+ // subscribe once; on later reloads just redraw the currently selected view
1350
+ if (this.isSubscribed) {
1351
+ this.updateDataType(this.pdService.isUserWise_data.value);
1352
+ return;
1353
+ }
1354
+ this.isSubscribed = true;
1306
1355
  this.pdService.chartType.subscribe(function (data) {
1307
1356
  if (data) {
1308
1357
  _this.updateChartType(data);
@@ -1311,10 +1360,25 @@
1311
1360
  this.pdService.isUserWise_data.subscribe(function (data) {
1312
1361
  _this.updateDataType(data);
1313
1362
  });
1363
+ this.pdService.isInsightsView.subscribe(function (insights) {
1364
+ if (!insights) {
1365
+ setTimeout(function () { return _this.fitChartHeight(); });
1366
+ }
1367
+ });
1314
1368
  window.addEventListener('resize', this.onResizeWindow.bind(this));
1315
1369
  };
1316
1370
  ChartviewComponent.prototype.onResizeWindow = function (event) {
1317
1371
  this.myChartwidth = window.innerWidth * 0.88; //set an appropriate factor
1372
+ this.fitChartHeight();
1373
+ };
1374
+ // stretch the chart area from where it starts down to the bottom of the window
1375
+ ChartviewComponent.prototype.fitChartHeight = function () {
1376
+ var chartview = document.getElementById('pd-chartview');
1377
+ if (!chartview || chartview.style.display === 'none')
1378
+ return;
1379
+ var top = chartview.getBoundingClientRect().top + window.scrollY;
1380
+ var height = Math.max(window.innerHeight - top - 8, 240);
1381
+ chartview.style.height = height + 'px';
1318
1382
  };
1319
1383
  ChartviewComponent.prototype.myChartReady = function () {
1320
1384
  this.createDivforTotalDurartion();
@@ -1322,16 +1386,21 @@
1322
1386
  // this.removeBlankEntryBar();
1323
1387
  };
1324
1388
  ChartviewComponent.prototype.updateChartType = function (dataType) {
1389
+ var _this = this;
1390
+ this.isTableView = dataType == angularGoogleCharts.ChartType.Table;
1325
1391
  if (this.chartview != null && this.chartview != undefined) {
1326
1392
  if (dataType == angularGoogleCharts.ChartType.Table) {
1327
1393
  this.chartview.style.display = 'none';
1328
1394
  }
1329
1395
  else {
1330
1396
  this.chartview.style.display = 'block';
1397
+ setTimeout(function () { return _this.fitChartHeight(); });
1331
1398
  }
1332
1399
  }
1333
1400
  };
1334
1401
  ChartviewComponent.prototype.updateDataType = function (dataType) {
1402
+ var _this = this;
1403
+ setTimeout(function () { return _this.fitChartHeight(); });
1335
1404
  this.isUserWise = dataType;
1336
1405
  if (this.myXmlData) {
1337
1406
  if (this.isUserWise) {
@@ -1359,7 +1428,7 @@
1359
1428
  ChartviewComponent.prototype.updateChart = function () {
1360
1429
  var _this = this;
1361
1430
  var labels = document.getElementById('labels');
1362
- while (labels.firstChild) {
1431
+ while (labels && labels.firstChild) {
1363
1432
  labels.removeChild(labels.lastChild);
1364
1433
  }
1365
1434
  this.chartDataTable = this.myChartDataMatrix;
@@ -1422,7 +1491,7 @@
1422
1491
  for (var i = 0; i < detailViewMatrix.length; i++) {
1423
1492
  // console.log("detailViewMatrix[i]",detailViewMatrix[i]);
1424
1493
  id_durationArray = {
1425
- name: detailViewMatrix[i].terminalIP,
1494
+ name: this.pdService.getRoomLabel(detailViewMatrix[i].terminalLocation, detailViewMatrix[i].terminalIP),
1426
1495
  value: parseInt(detailViewMatrix[i].duration),
1427
1496
  };
1428
1497
  id_durationMatrix.push(id_durationArray);
@@ -1482,6 +1551,8 @@
1482
1551
  var rowLabels = [];
1483
1552
  this.rowElements = [];
1484
1553
  var labels = document.getElementById('labels');
1554
+ if (!labels)
1555
+ return;
1485
1556
  while (labels.firstChild) {
1486
1557
  labels.removeChild(labels.lastChild);
1487
1558
  }
@@ -1524,14 +1595,83 @@
1524
1595
  }
1525
1596
  }
1526
1597
  };
1598
+ // rows for lib-timeline, one per teacher or room, bars point back into `list` by index
1599
+ ChartviewComponent.prototype.buildTimeline = function (list) {
1600
+ var _this = this;
1601
+ this.timelineList = list || [];
1602
+ var rows = [];
1603
+ var rowByLabel = {};
1604
+ this.timelineList.forEach(function (session, index) {
1605
+ var label = _this.isUserWise
1606
+ ? session.userName
1607
+ : _this.pdService.getRoomLabel(session.terminalLocation, session.terminalIP);
1608
+ if (label === undefined || label === null || label === '' || label === '--')
1609
+ return;
1610
+ if (label === '-')
1611
+ label = 'Unknown user';
1612
+ var row = rowByLabel[label];
1613
+ if (!row) {
1614
+ row = rowByLabel[label] = { label: label, bars: [] };
1615
+ rows.push(row);
1616
+ }
1617
+ if (_this.isSession(session)) {
1618
+ var other = _this.isUserWise
1619
+ ? _this.pdService.getRoomLabel(session.terminalLocation, session.terminalIP)
1620
+ : session.userName;
1621
+ var time = session.startTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) +
1622
+ ' – ' + session.endTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
1623
+ row.bars.push({
1624
+ start: session.startTime,
1625
+ end: session.endTime,
1626
+ index: index,
1627
+ tooltip: ((other || '') + "\n" + time).trim(),
1628
+ });
1629
+ }
1630
+ });
1631
+ this.timelineRows = rows;
1632
+ };
1633
+ // idle user / room placeholders have no user or no real start time
1634
+ ChartviewComponent.prototype.isSession = function (session) {
1635
+ return !!session && !!session.userId &&
1636
+ session.startTime instanceof Date && !isNaN(session.startTime.getTime()) &&
1637
+ session.endTime instanceof Date && !isNaN(session.endTime.getTime());
1638
+ };
1639
+ // every session of one row: a teacher's day (user wise) or a room's day (classroom wise)
1640
+ ChartviewComponent.prototype.openRow = function (event) {
1641
+ var _this = this;
1642
+ var sessions = event.indexes
1643
+ .map(function (i) { return _this.timelineList[i]; })
1644
+ .filter(function (s) { return _this.isSession(s); })
1645
+ .map(function (s) { return ({ userId: s.userId, ip: s.terminalIP, start: s.startTime.getTime() }); });
1646
+ if (!sessions.length)
1647
+ return;
1648
+ this.pdService.detailAccentColor = event.color;
1649
+ this.pdService.detailRowRequest = { label: event.label, isUserWise: this.isUserWise, total: event.total, sessions: sessions };
1650
+ this.pdService.openSmallTable.next({ allSessions: true });
1651
+ };
1652
+ ChartviewComponent.prototype.openSession = function (event) {
1653
+ var index = event.index;
1654
+ var session = this.timelineList[index];
1655
+ if (!this.isSession(session))
1656
+ return;
1657
+ this.pdService.detailAccentColor = event.color;
1658
+ this.pdService.detailRowRequest = null;
1659
+ this.selectedUserData = this.pdService.dataForDetailedView(this.timelineList, this.resourceDataList, index);
1660
+ this.pdService.detailedviewData.next(this.selectedUserData);
1661
+ this.pdService.openSmallTable.next(this.selectedUserData);
1662
+ };
1527
1663
  ChartviewComponent.prototype.onSelectRowData = function (event) {
1528
1664
  var _a = event.selection[0], row = _a.row, column = _a.column;
1665
+ // pick the same list the chart rows were built from
1666
+ var selectedList = this.currentdataList.userConsoleData;
1529
1667
  if (this.isAllUsers) {
1530
- this.selectedUserData = this.pdService.dataForDetailedView(this.alluserList.fordurationTable, this.resourceDataList, row);
1668
+ selectedList = this.isUserWise ? this.alluserList.fordurationTable : this.allRoomsList.fordurationTable;
1531
1669
  }
1532
- else {
1533
- this.selectedUserData = this.pdService.dataForDetailedView(this.currentdataList.userConsoleData, this.resourceDataList, row);
1670
+ // idle room / user rows have no session to show
1671
+ if (!selectedList[row] || !selectedList[row].userId || !selectedList[row].startTime) {
1672
+ return;
1534
1673
  }
1674
+ this.selectedUserData = this.pdService.dataForDetailedView(selectedList, this.resourceDataList, row);
1535
1675
  var smallData = [{ 'a': this.currentdataList, 'b': row }];
1536
1676
  // console.log("this.selectedUserData",this.selectedUserData);
1537
1677
  this.pdService.detailedviewData.next(this.selectedUserData);
@@ -1561,6 +1701,7 @@
1561
1701
  };
1562
1702
  ChartviewComponent.prototype.userWiseCurrentData = function () {
1563
1703
  var chartDataUserWise = this.pdService.userWiseChartData();
1704
+ this.buildTimeline(this.currentdataList.userConsoleData);
1564
1705
  // console.log("chartDataUserWise",chartDataUserWise);
1565
1706
  if (chartDataUserWise.length > 0) {
1566
1707
  this.myChartDataMatrix = chartDataUserWise;
@@ -1571,6 +1712,7 @@
1571
1712
  }
1572
1713
  };
1573
1714
  ChartviewComponent.prototype.userWiseAllData = function () {
1715
+ this.buildTimeline(this.alluserList.fordurationTable);
1574
1716
  var chartDataUserWise = this.alluserList.forChartData;
1575
1717
  if (chartDataUserWise.length > 0) {
1576
1718
  this.myChartDataMatrix = chartDataUserWise;
@@ -1580,6 +1722,7 @@
1580
1722
  };
1581
1723
  ChartviewComponent.prototype.classRoomWiseCurrentData = function () {
1582
1724
  var classRoomdata = this.pdService.classRoomWiseChartData();
1725
+ this.buildTimeline(this.currentdataList.userConsoleData);
1583
1726
  console.log("classRoomdata", classRoomdata);
1584
1727
  if (classRoomdata && classRoomdata.length > 0) {
1585
1728
  this.myChartDataMatrix = classRoomdata;
@@ -1591,7 +1734,8 @@
1591
1734
  }
1592
1735
  };
1593
1736
  ChartviewComponent.prototype.classRoomWiseAllData = function () {
1594
- var allclassRoomList = this.pdService.allRoomsData();
1737
+ var allclassRoomList = this.allRoomsList || this.pdService.allRoomsData();
1738
+ this.buildTimeline(allclassRoomList.fordurationTable);
1595
1739
  if (allclassRoomList.forChartData && allclassRoomList.forChartData.length > 0) {
1596
1740
  this.myChartDataMatrix = allclassRoomList.forChartData;
1597
1741
  this.updateChart();
@@ -1642,8 +1786,8 @@
1642
1786
  ChartviewComponent.decorators = [
1643
1787
  { type: i0.Component, args: [{
1644
1788
  selector: 'lib-chartview',
1645
- template: "<div id=\"pd-chartview-main-container\" class=\"pd-chartview-main-container\">\n <div id=\"userSummary\">\n <table class=\"summaryTable\">\n <tbody>\n <tr>\n <td id=\"totalUsers\" class=\"chartview-navigationbar\">\n Total users: {{totalUsers}}\n </td>\n <td id=\"eligibleUsers\" class=\"chartview-navigationbar\">\n Eligible users: {{eligibleUsers}}\n </td>\n <td id=\"currentUsers\" class=\"chartview-navigationbar\">\n Current users: {{currentUsers}}\n </td>\n <td id=\"totalRooms\" class=\"chartview-navigationbar\">\n Installed Classrooms: {{installedClassrooms}}\n </td>\n <td id=\"currentRooms\" class=\"chartview-navigationbar\">\n Current Classrooms: {{currentRooms}}\n </td>\n <!-- <td\n id=\"currentRooms\"\n \n >\n <span id=\"totalUsers_list\" class=\"activeAll\">User List</span>\n </td> -->\n </tr>\n </tbody>\n </table>\n </div>\n <div id=\"pd-messageDivContainer\" class='pd-messageDivContainer'>\n <div id=\"pd-messageDiv\" class='pd-messageDiv'>Click the bars to view details of content accessed by a teacher.</div>\n <div id=\"pd-toggleContainer\" class=\"pd-toggleContainer\">\n \n <div id=\"pd-currentUserBtn\" class=\"pd-currentUserBtn-div pd-commonStyleBtn\" (click)=\"showCurrentUser()\" [ngStyle]=\"{ color: isAllUsers ? '#000000' : '#ffffff' , backgroundColor: isAllUsers ? '#c4c4c4' : '#000000'}\">\n {{currentList}}</div>\n <div id=\"pd-allUserBtn\" class=\"pd-allUserBtn-div pd-commonStyleBtn\" (click)=\"showAllUser()\" [ngStyle]=\"{ color: isAllUsers ? '#ffffff' : '#000000' , backgroundColor: isAllUsers ? '#000000' : '#c4c4c4'}\">\n {{allList}}</div>\n </div>\n </div>\n <div id=\"pd-chartview\" class=\"pd-chartview\">\n <div id=\"myChartDiv\">\n <div id=\"myTimeLine\">\n <google-chart\n [type]=\"myChartType\"\n [data]=\"chartDataTable\"\n [options]=\"myChartOptions\"\n [width]=\"myChartwidth\"\n [height]=\"myChartHeight\"\n (select)=\"onSelectRowData($event)\"\n (ready)=\"myChartReady()\"\n > \n </google-chart>\n \n </div>\n <div class=\"inline babloo\" id=\"labels\"></div> \n \n \n </div>\n</div>\n",
1646
- styles: [".pd-chartview-main-container .pd-chartview{display:block;overflow:scroll;width:100%;height:484px;font-family:Tahoma;font-size:12pt;font-weight:400;color:#666;margin:10 auto;text-align:center;padding-top:1px}.pd-chartview-main-container #userSummary{background:#e0e0e0;margin-top:1px}.pd-chartview-main-container #roomSummary,.pd-chartview-main-container #userSummary{position:relative;width:100%;text-align:center;padding-top:6px;padding-bottom:6px;font-size:13px}.pd-chartview-main-container .pd-messageDivContainer{margin-top:1px}.pd-chartview-main-container .pd-messageDivContainer .pd-messageDiv{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;position:absolute;margin-left:5px;font-size:13px}.pd-chartview-main-container .pd-messageDivContainer .pd-toggleContainer{line-height:30px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;float:right;margin:5px 0}.pd-chartview-main-container .pd-messageDivContainer .pd-allUserBtn-div{border-radius:0 5px 5px 0}.pd-chartview-main-container .pd-messageDivContainer .pd-currentUserBtn-div{border-radius:5px 0 0 5px}.pd-chartview-main-container .pd-messageDivContainer .pd-commonStyleBtn{display:inline-block;cursor:pointer;font-size:12px;width:92px;text-align:center}.pd-chartview-main-container .summaryTable{width:100%;border-collapse:collapse;border-spacing:2px;border-color:grey}.pd-chartview-main-container .activeAll{cursor:pointer;background:none!important;border-radius:13px;border:none;width:64px;height:18px!important;line-height:18px!important;color:#45a1da;margin-right:0!important;padding:1px 5px;vertical-align:sub}.pd-chartview-main-container #myTimeLine{width:89%;height:60%;margin-left:5px;overflow-x:scroll;overflow-y:hidden;float:left}.pd-chartview-main-container #myTimeLine2{width:71px;position:relative;overflow-x:hidden;overflow-y:hidden}.pd-chartview-main-container #myChartDiv{width:100%}.pd-chartview-main-container .chartview-navigationbar{border-collapse:collapse;border-right:1px solid #999;text-align:center}.pd-chartview-main-container #labels{border-right:1px solid #b7b7b7;border-top:1px solid #b7b7b7;width:10%;float:left;font-size:1.2vw;overflow-x:hidden;overflow-y:hidden}.inline{vertical-align:top;position:relative}"]
1789
+ template: "<div id=\"pd-chartview-main-container\" class=\"pd-chartview-main-container\">\n <div id=\"userSummary\">\n <table class=\"summaryTable\">\n <tbody>\n <tr>\n <td id=\"totalUsers\" class=\"chartview-navigationbar\">\n Total users: {{totalUsers}}\n </td>\n <td id=\"eligibleUsers\" class=\"chartview-navigationbar\">\n Eligible users: {{eligibleUsers}}\n </td>\n <td id=\"currentUsers\" class=\"chartview-navigationbar\">\n Current users: {{currentUsers}}\n </td>\n <td id=\"totalRooms\" class=\"chartview-navigationbar\">\n Installed Classrooms: {{installedClassrooms}}\n </td>\n <td id=\"currentRooms\" class=\"chartview-navigationbar\">\n Current Classrooms: {{currentRooms}}\n </td>\n <!-- <td\n id=\"currentRooms\"\n \n >\n <span id=\"totalUsers_list\" class=\"activeAll\">User List</span>\n </td> -->\n </tr>\n </tbody>\n </table>\n </div>\n <div id=\"pd-messageDivContainer\" class='pd-messageDivContainer'>\n <div id=\"pd-messageDiv\" class='pd-messageDiv' *ngIf=\"!isTableView\">Click the bars to view details of content accessed by a teacher.</div>\n <div id=\"pd-toggleContainer\" class=\"pd-toggleContainer\">\n \n <div id=\"pd-currentUserBtn\" class=\"pd-currentUserBtn-div pd-commonStyleBtn\" (click)=\"showCurrentUser()\" [ngStyle]=\"{ color: isAllUsers ? '#000000' : '#ffffff' , backgroundColor: isAllUsers ? '#c4c4c4' : '#000000'}\">\n {{currentList}}</div>\n <div id=\"pd-allUserBtn\" class=\"pd-allUserBtn-div pd-commonStyleBtn\" (click)=\"showAllUser()\" [ngStyle]=\"{ color: isAllUsers ? '#ffffff' : '#000000' , backgroundColor: isAllUsers ? '#000000' : '#c4c4c4'}\">\n {{allList}}</div>\n </div>\n </div>\n <div id=\"pd-chartview\" class=\"pd-chartview\">\n <lib-timeline\n *ngIf=\"useCustomTimeline\"\n [rows]=\"timelineRows\"\n [labelTitle]=\"isUserWise ? 'Teacher' : 'Room'\"\n (barClick)=\"openSession($event)\"\n (totalClick)=\"openRow($event)\"\n ></lib-timeline>\n <div id=\"myChartDiv\" *ngIf=\"!useCustomTimeline\">\n <div id=\"myTimeLine\">\n <google-chart\n [type]=\"myChartType\"\n [data]=\"chartDataTable\"\n [options]=\"myChartOptions\"\n [width]=\"myChartwidth\"\n [height]=\"myChartHeight\"\n (select)=\"onSelectRowData($event)\"\n (ready)=\"myChartReady()\"\n > \n </google-chart>\n \n </div>\n <div class=\"inline babloo\" id=\"labels\"></div> \n \n \n </div>\n</div>\n",
1790
+ styles: [".pd-chartview-main-container .pd-chartview{display:block;overflow:scroll;width:100%;height:484px;font-family:Tahoma;font-size:12pt;font-weight:400;color:#666;margin:10 auto;text-align:center;padding-top:1px}.pd-chartview-main-container #userSummary{background:#e0e0e0;margin-top:1px}.pd-chartview-main-container #roomSummary,.pd-chartview-main-container #userSummary{position:relative;width:100%;text-align:center;padding-top:6px;padding-bottom:6px;font-size:13px}.pd-chartview-main-container .pd-messageDivContainer{margin-top:1px;position:relative;display:flex;align-items:center;justify-content:center;min-height:40px;padding:0 200px;box-sizing:border-box}.pd-chartview-main-container .pd-messageDivContainer .pd-messageDiv{font-size:13px;text-align:center}.pd-chartview-main-container .pd-messageDivContainer .pd-toggleContainer{line-height:30px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;position:absolute;right:0;top:50%;transform:translateY(-50%)}.pd-chartview-main-container .pd-messageDivContainer .pd-allUserBtn-div{border-radius:0 5px 5px 0}.pd-chartview-main-container .pd-messageDivContainer .pd-currentUserBtn-div{border-radius:5px 0 0 5px}.pd-chartview-main-container .pd-messageDivContainer .pd-commonStyleBtn{display:inline-block;cursor:pointer;font-size:12px;width:92px;text-align:center}.pd-chartview-main-container .summaryTable{width:100%;border-collapse:collapse;border-spacing:2px;border-color:grey}.pd-chartview-main-container .activeAll{cursor:pointer;background:none!important;border-radius:13px;border:none;width:64px;height:18px!important;line-height:18px!important;color:#45a1da;margin-right:0!important;padding:1px 5px;vertical-align:sub}.pd-chartview-main-container #myTimeLine{width:89%;height:60%;margin-left:5px;overflow-x:scroll;overflow-y:hidden;float:left}.pd-chartview-main-container #myTimeLine2{width:71px;position:relative;overflow-x:hidden;overflow-y:hidden}.pd-chartview-main-container #myChartDiv{width:100%}.pd-chartview-main-container .chartview-navigationbar{border-collapse:collapse;border-right:1px solid #999;text-align:center}.pd-chartview-main-container #labels{border-right:1px solid #b7b7b7;border-top:1px solid #b7b7b7;width:10%;float:left;font-size:1.2vw;overflow-x:hidden;overflow-y:hidden}.inline{vertical-align:top;position:relative}"]
1647
1791
  },] }
1648
1792
  ];
1649
1793
  ChartviewComponent.ctorParameters = function () { return [
@@ -2909,13 +3053,22 @@
2909
3053
  this.remaningUsers = [];
2910
3054
  this.allUsers = [];
2911
3055
  this.usersCheckboxChecked = false;
3056
+ this.classroomArray = [];
3057
+ this.allClassrooms = [];
2912
3058
  this.isDisplay = false;
3059
+ this.isUserWise = true;
3060
+ this.isAllUsers = false;
3061
+ this.isInitialized = false;
2913
3062
  }
2914
3063
  Object.defineProperty(FullTableViewComponent.prototype, "data", {
2915
3064
  set: function (res) {
2916
3065
  if (res !== null) {
2917
3066
  console.log('newtable', JSON.parse(res));
2918
3067
  this.jsonResponse = JSON.parse(res);
3068
+ // rebuild rows when new data arrives (date change / refresh)
3069
+ if (this.isInitialized) {
3070
+ this.loadUserData();
3071
+ }
2919
3072
  }
2920
3073
  },
2921
3074
  enumerable: false,
@@ -2924,6 +3077,13 @@
2924
3077
  FullTableViewComponent.prototype.ngOnInit = function () {
2925
3078
  var _this = this;
2926
3079
  this.loadUserData();
3080
+ this.isInitialized = true;
3081
+ this.pdService.isUserWise_data.subscribe(function (data) {
3082
+ _this.isUserWise = data;
3083
+ });
3084
+ this.pdService.isAllUsers.subscribe(function (data) {
3085
+ _this.isAllUsers = !!data;
3086
+ });
2927
3087
  this.pdService.chartType.subscribe(function (data) {
2928
3088
  if (data) {
2929
3089
  console.log('newTable', data);
@@ -2931,9 +3091,30 @@
2931
3091
  }
2932
3092
  });
2933
3093
  };
3094
+ Object.defineProperty(FullTableViewComponent.prototype, "userRows", {
3095
+ get: function () {
3096
+ return this.isAllUsers ? this.allUsers : this.currentUserArray;
3097
+ },
3098
+ enumerable: false,
3099
+ configurable: true
3100
+ });
3101
+ Object.defineProperty(FullTableViewComponent.prototype, "classroomRows", {
3102
+ get: function () {
3103
+ return this.isAllUsers ? this.allClassrooms : this.classroomArray;
3104
+ },
3105
+ enumerable: false,
3106
+ configurable: true
3107
+ });
2934
3108
  FullTableViewComponent.prototype.loadUserData = function () {
2935
3109
  var _this = this;
2936
3110
  var json = this.jsonResponse;
3111
+ this.currentUserArray = [];
3112
+ this.remaningUsers = [];
3113
+ this.allUsers = [];
3114
+ this.classroomArray = [];
3115
+ this.allClassrooms = [];
3116
+ if (!json)
3117
+ return;
2937
3118
  var usersConsole = json.usersConsole.userConsole;
2938
3119
  var usersMetadata = json.usersMetadata.userMetadata;
2939
3120
  var resourcesMetadata = json.resourcesMetadata.resourceMetadata;
@@ -3013,8 +3194,96 @@
3013
3194
  };
3014
3195
  });
3015
3196
  this.allUsers = __spread(this.currentUserArray, this.remaningUsers);
3197
+ this.loadClassroomData(usersConsole, usersMetadata, resourcesMetadata);
3016
3198
  this.loadTable(this.currentUserArray);
3017
3199
  };
3200
+ // Classroom wise rows: grouped by room, then by session start time
3201
+ FullTableViewComponent.prototype.loadClassroomData = function (usersConsole, usersMetadata, resourcesMetadata) {
3202
+ var _this = this;
3203
+ var _a, _b;
3204
+ var schoolTerminals = ((_b = (_a = this.jsonResponse) === null || _a === void 0 ? void 0 : _a.schoolTerminals) === null || _b === void 0 ? void 0 : _b.schoolTerminal) || [];
3205
+ var roomNames = {};
3206
+ schoolTerminals.forEach(function (terminal) {
3207
+ roomNames[terminal.terminalKey] = _this.getTerminalLocation(terminal);
3208
+ });
3209
+ var sessionsByRoom = {};
3210
+ // every session is a bar in the classroom chart, so keep sessions without resources too
3211
+ usersConsole.forEach(function (user) {
3212
+ var ip = (user === null || user === void 0 ? void 0 : user.terminalIP) || '';
3213
+ (sessionsByRoom[ip] = sessionsByRoom[ip] || []).push(user);
3214
+ });
3215
+ var roomLabel = function (ip) { return _this.pdService.getRoomLabel(roomNames[ip], ip); };
3216
+ var activeIPs = Object.keys(sessionsByRoom).sort(function (a, b) { return roomLabel(a).localeCompare(roomLabel(b), undefined, { numeric: true }); });
3217
+ activeIPs.forEach(function (ip) {
3218
+ var sessions = sessionsByRoom[ip].sort(function (a, b) { return new Date(a.startTime).getTime() - new Date(b.startTime).getTime(); });
3219
+ sessions.forEach(function (user, sessionIndex) {
3220
+ var _a;
3221
+ var userDetails = usersMetadata.find(function (userMeta) { return userMeta.userId === user.userId; });
3222
+ var startTime = new Date(user === null || user === void 0 ? void 0 : user.startTime);
3223
+ var endTime = new Date(user === null || user === void 0 ? void 0 : user.endTime);
3224
+ var totalDuration = _this.formatTime(Math.round((endTime.getTime() - startTime.getTime()) / 1000));
3225
+ var resources = ((_a = user === null || user === void 0 ? void 0 : user.userConsoleResources) === null || _a === void 0 ? void 0 : _a.userConsoleResource) || [];
3226
+ var sessionRows = resources.length > 0 ? resources : [null];
3227
+ sessionRows.forEach(function (userResource, resourceIndex) {
3228
+ var _a, _b, _c, _d;
3229
+ var resourceDetails = userResource && resourcesMetadata.find(function (resource) { return resource.resourceId === userResource.resourceId &&
3230
+ userResource.type === resource.type; });
3231
+ var isFirstOfSession = resourceIndex === 0;
3232
+ _this.classroomArray.push({
3233
+ room: isFirstOfSession && sessionIndex === 0 ? roomLabel(ip) : '',
3234
+ teacherName: isFirstOfSession ? userDetails === null || userDetails === void 0 ? void 0 : userDetails.fullName : '',
3235
+ date: isFirstOfSession ? user === null || user === void 0 ? void 0 : user.selectedDate : '',
3236
+ startTime: isFirstOfSession ? _this.formatDate(startTime) : '',
3237
+ endtime: isFirstOfSession ? _this.formatDate(endTime) : '',
3238
+ totalDuration: isFirstOfSession ? totalDuration : '',
3239
+ tp: (_a = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.teachingPoint) === null || _a === void 0 ? void 0 : _a.title,
3240
+ subject: (_b = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.subject) === null || _b === void 0 ? void 0 : _b.title,
3241
+ chapter: (_c = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.chapter) === null || _c === void 0 ? void 0 : _c.title,
3242
+ grade: (_d = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.grade) === null || _d === void 0 ? void 0 : _d.title,
3243
+ details: userResource ? _this.getAssetDetails(userResource, resourceDetails) : '',
3244
+ });
3245
+ });
3246
+ });
3247
+ });
3248
+ var idleRooms = schoolTerminals
3249
+ .filter(function (terminal) { return !sessionsByRoom[terminal.terminalKey]; })
3250
+ .map(function (terminal) { return ({
3251
+ room: roomLabel(terminal.terminalKey),
3252
+ teacherName: '',
3253
+ date: '',
3254
+ startTime: '',
3255
+ endtime: '',
3256
+ totalDuration: '0s',
3257
+ tp: '',
3258
+ subject: '',
3259
+ chapter: '',
3260
+ grade: '',
3261
+ details: '',
3262
+ }); });
3263
+ this.allClassrooms = __spread(this.classroomArray, idleRooms);
3264
+ };
3265
+ FullTableViewComponent.prototype.getTerminalLocation = function (terminal) {
3266
+ var _a;
3267
+ try {
3268
+ return (terminal === null || terminal === void 0 ? void 0 : terminal.metadata) ? ((_a = JSON.parse(terminal.metadata)) === null || _a === void 0 ? void 0 : _a.location) || '' : '';
3269
+ }
3270
+ catch (e) {
3271
+ return '';
3272
+ }
3273
+ };
3274
+ FullTableViewComponent.prototype.getAssetDetails = function (userResource, resourceDetails) {
3275
+ var _a, _b;
3276
+ var prefix = this.getDisplayTypeValue(userResource.type);
3277
+ var title = (resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.title) || '';
3278
+ var duration = parseInt((userResource === null || userResource === void 0 ? void 0 : userResource.duration) || '0');
3279
+ if (((_a = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _a === void 0 ? void 0 : _a.includes('question')) || ((_b = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _b === void 0 ? void 0 : _b.includes('cquestion'))) {
3280
+ duration = 0;
3281
+ }
3282
+ else {
3283
+ duration = duration === 0 ? "<span class='seenTag' style='color:blue;'>(Seen)</span>" : this.formatTime(duration);
3284
+ }
3285
+ return title === '' ? prefix + " : " + duration : prefix + " : " + title + " : " + duration;
3286
+ };
3018
3287
  FullTableViewComponent.prototype.getDisplayTypeValue = function (key) {
3019
3288
  var assetTypeConfig = [
3020
3289
  { type: 'asset_media', display: 'Media' },
@@ -3092,8 +3361,8 @@
3092
3361
  FullTableViewComponent.decorators = [
3093
3362
  { type: i0.Component, args: [{
3094
3363
  selector: 'lib-full-table-view',
3095
- template: "\n<div class=\"tableViewList\" *ngIf=\"isDisplay\">\n <!-- <label for=\"usersCheckbox\">All</label>\n <input\n type=\"checkbox\"\n id=\"usersCheckbox\"\n [checked]=\"usersCheckboxChecked\"\n (change)=\"toggleUsers()\"\n /> -->\n <div class=\"tableArea\">\n \n <table id=\"teacherTable\">\n <thead class=\"tablehead\">\n <tr>\n <th>Teacher Name</th>\n <th>Subjects</th>\n <th>Date</th>\n <th>Start Time</th>\n <th>End Time</th>\n <th>Total Duration</th>\n <th>Room</th>\n <th>Topic</th>\n <th>Subject</th>\n <th>Chapter</th>\n <th>Grade</th>\n <th>Details</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let user of currentUserArray\">\n <td>{{ user.teacherName }}</td>\n <td>{{ user.subjects }}</td>\n <td>{{ user.date }}</td>\n <td>{{ user.startTime }}</td>\n <td>{{ user.endtime }}</td>\n <td>{{ user.totalDuration }}</td>\n <td>{{ user.room }}</td>\n <td>{{ user.tp }}</td>\n <td>{{ user.subject }}</td>\n <td>{{ user.chapter }}</td>\n <td>{{ user.grade }}</td>\n <td><div [innerHTML]=\"user.details\"></div></td>\n </tr>\n </tbody>\n </table>\n </div>\n \n</div>\n ",
3096
- styles: ["body{font-family:Arial,sans-serif;margin:20px}.tablehead{position:sticky;top:0;background:#fff}table{width:100%;border-collapse:collapse}td,th{border:1px solid #ddd;padding:8px;text-align:left}th{background-color:#f4f4f4;font-weight:700}tr:nth-child(2n){background-color:#f9f9f9}tr:hover{background-color:#f1f1f1}.tableViewList{position:absolute;margin-top:20px}.tableArea{height:700px;overflow:scroll;font-size:12px;margin:20px 5px 5px}.seenTag{color:#00f}"]
3364
+ template: "\n<div class=\"tableViewList\" *ngIf=\"isDisplay\">\n <!-- <label for=\"usersCheckbox\">All</label>\n <input\n type=\"checkbox\"\n id=\"usersCheckbox\"\n [checked]=\"usersCheckboxChecked\"\n (change)=\"toggleUsers()\"\n /> -->\n <div class=\"tableArea\">\n \n <table id=\"teacherTable\" *ngIf=\"isUserWise\">\n <thead class=\"tablehead\">\n <tr>\n <th>Teacher Name</th>\n <th>Subjects</th>\n <th>Date</th>\n <th>Start Time</th>\n <th>End Time</th>\n <th>Total Duration</th>\n <th>Room</th>\n <th>Topic</th>\n <th>Subject</th>\n <th>Chapter</th>\n <th>Grade</th>\n <th>Details</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let user of userRows\">\n <td>{{ user.teacherName }}</td>\n <td>{{ user.subjects }}</td>\n <td>{{ user.date }}</td>\n <td>{{ user.startTime }}</td>\n <td>{{ user.endtime }}</td>\n <td>{{ user.totalDuration }}</td>\n <td>{{ user.room }}</td>\n <td>{{ user.tp }}</td>\n <td>{{ user.subject }}</td>\n <td>{{ user.chapter }}</td>\n <td>{{ user.grade }}</td>\n <td><div [innerHTML]=\"user.details\"></div></td>\n </tr>\n </tbody>\n </table>\n\n <table id=\"classroomTable\" *ngIf=\"!isUserWise\">\n <thead class=\"tablehead\">\n <tr>\n <th>Room</th>\n <th>Teacher Name</th>\n <th>Date</th>\n <th>Start Time</th>\n <th>End Time</th>\n <th>Total Duration</th>\n <th>Topic</th>\n <th>Subject</th>\n <th>Chapter</th>\n <th>Grade</th>\n <th>Details</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of classroomRows\" [class.roomStart]=\"row.room\">\n <td class=\"roomCell\">{{ row.room }}</td>\n <td>{{ row.teacherName }}</td>\n <td>{{ row.date }}</td>\n <td>{{ row.startTime }}</td>\n <td>{{ row.endtime }}</td>\n <td>{{ row.totalDuration }}</td>\n <td>{{ row.tp }}</td>\n <td>{{ row.subject }}</td>\n <td>{{ row.chapter }}</td>\n <td>{{ row.grade }}</td>\n <td><div [innerHTML]=\"row.details\"></div></td>\n </tr>\n </tbody>\n </table>\n </div>\n \n</div>\n ",
3365
+ styles: ["body{font-family:Arial,sans-serif;margin:20px}.tablehead{position:sticky;top:0;background:#fff}table{width:100%;border-collapse:collapse}td,th{border:1px solid #ddd;padding:8px;text-align:left}th{background-color:#f4f4f4;font-weight:700}tr:nth-child(2n){background-color:#f9f9f9}tr:hover{background-color:#f1f1f1}.tableViewList{position:absolute;margin-top:20px}.tableArea{height:700px;overflow:scroll;font-size:12px;margin:20px 5px 5px}.seenTag{color:#00f}.roomCell{font-weight:700}.roomStart td{border-top:2px solid #b7b7b7}"]
3097
3366
  },] }
3098
3367
  ];
3099
3368
  FullTableViewComponent.ctorParameters = function () { return [
@@ -3112,6 +3381,15 @@
3112
3381
  this.allUsers = [];
3113
3382
  this.usersCheckboxChecked = false;
3114
3383
  this.isDisplay = false;
3384
+ this.accentColor = '';
3385
+ // 'session' = one bar was clicked; 'all' = a row's total was clicked
3386
+ this.mode = 'session';
3387
+ this.allTitle = '';
3388
+ this.allSummary = [];
3389
+ this.otherColumn = 'Room';
3390
+ // who used the room (classroom wise) or which rooms were used (user wise), with time each
3391
+ this.people = [];
3392
+ this.peopleTitle = '';
3115
3393
  }
3116
3394
  Object.defineProperty(SmallTableViewComponent.prototype, "data", {
3117
3395
  set: function (res) {
@@ -3128,6 +3406,12 @@
3128
3406
  this.pdService.openSmallTable$.subscribe(function (data) {
3129
3407
  if (data) {
3130
3408
  _this.isDisplay = true;
3409
+ _this.accentColor = _this.pdService.detailAccentColor;
3410
+ if (data['allSessions'] && _this.pdService.detailRowRequest) {
3411
+ _this.loadRowSessions(_this.pdService.detailRowRequest);
3412
+ return;
3413
+ }
3414
+ _this.mode = 'session';
3131
3415
  var userid = _this.findUserIdByName(data['userDetails'][0]);
3132
3416
  var startTime = data['userDetails'][1];
3133
3417
  var endTime = data['userDetails'][3];
@@ -3159,55 +3443,8 @@
3159
3443
  ? usersConsole.filter(function (user) { return user.userId === userId; })
3160
3444
  : usersConsole;
3161
3445
  filteredUsersConsole.forEach(function (user) {
3162
- var _a;
3163
- var userDetails = usersMetadata.find(function (userMeta) { return userMeta.userId === user.userId; });
3164
- if (user === null || user === void 0 ? void 0 : user.userConsoleResources) {
3165
- var userConsoleResource = (_a = user.userConsoleResources) === null || _a === void 0 ? void 0 : _a.userConsoleResource;
3166
- if (userConsoleResource && userConsoleResource.length > 0) {
3167
- userConsoleResource.forEach(function (userResource, userIndex) {
3168
- var _a, _b, _c, _d, _e, _f;
3169
- var resourceId = userResource.resourceId;
3170
- var resourceDetails = resourcesMetadata.find(function (resource) { return resource.resourceId === resourceId &&
3171
- userResource.type === resource.type; });
3172
- var prefix = _this.getDisplayTypeValue(userResource.type);
3173
- var title = (resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.title) || '';
3174
- var tp = _this.decodeHtmlEntities((_a = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.teachingPoint) === null || _a === void 0 ? void 0 : _a.title);
3175
- var subject = _this.decodeHtmlEntities((_b = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.subject) === null || _b === void 0 ? void 0 : _b.title);
3176
- var chapter = _this.decodeHtmlEntities((_c = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.chapter) === null || _c === void 0 ? void 0 : _c.title);
3177
- var grade = _this.decodeHtmlEntities((_d = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.grade) === null || _d === void 0 ? void 0 : _d.title);
3178
- var startTime = new Date(user === null || user === void 0 ? void 0 : user.startTime);
3179
- var endTime = new Date(user === null || user === void 0 ? void 0 : user.endTime);
3180
- var totalDuration = _this.formatTime(Math.round((endTime.getTime() - startTime.getTime()) / 1000));
3181
- var duration = parseInt((userResource === null || userResource === void 0 ? void 0 : userResource.duration) || '0');
3182
- //duration = duration === 0 ? "<span class='dddd'>(Seen)</span>" : this.formatTime(duration);
3183
- if (((_e = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _e === void 0 ? void 0 : _e.includes('question')) || ((_f = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _f === void 0 ? void 0 : _f.includes('cquestion'))) {
3184
- duration = 0;
3185
- }
3186
- else {
3187
- duration = duration === 0 ? "<span class='dddd'>(Seen)</span>" : _this.formatTime(duration);
3188
- }
3189
- var finalAssetTitle = prefix + " : " + title + " : " + duration;
3190
- if (title === '') {
3191
- finalAssetTitle = prefix + " : " + duration;
3192
- }
3193
- _this.currentUserArray.push({
3194
- userId: user.userId,
3195
- teacherName: userIndex === 0 ? userDetails === null || userDetails === void 0 ? void 0 : userDetails.fullName : '',
3196
- subjects: userIndex === 0 ? userDetails === null || userDetails === void 0 ? void 0 : userDetails.userSubjects : '',
3197
- date: userIndex === 0 ? user === null || user === void 0 ? void 0 : user.selectedDate : '',
3198
- startTime: userIndex === 0 ? _this.formatDate(startTime) : _this.formatDate(startTime),
3199
- endtime: userIndex === 0 ? _this.formatDate(endTime) : _this.formatDate(endTime),
3200
- totalDuration: userIndex === 0 ? totalDuration : '',
3201
- room: userIndex === 0 ? user === null || user === void 0 ? void 0 : user.terminalIP : '',
3202
- tp: tp,
3203
- subject: subject,
3204
- chapter: chapter,
3205
- grade: grade,
3206
- details: finalAssetTitle,
3207
- });
3208
- });
3209
- }
3210
- }
3446
+ var _g;
3447
+ (_g = _this.currentUserArray).push.apply(_g, __spread(_this.rowsForConsole(user, usersMetadata, resourcesMetadata)));
3211
3448
  });
3212
3449
  var currentUserIds = this.currentUserArray
3213
3450
  .map(function (x) { return x.userId; })
@@ -3235,6 +3472,148 @@
3235
3472
  this.currentTimeFrameUserArray = this.filterDataByCriteria(this.currentUserArray, userId, inputStartTime, inputEndTime);
3236
3473
  console.log('smallData', this.currentTimeFrameUserArray);
3237
3474
  };
3475
+ // every session of a timeline row, oldest first; sessions with nothing opened still get a line
3476
+ SmallTableViewComponent.prototype.loadRowSessions = function (request) {
3477
+ var _this = this;
3478
+ this.mode = 'all';
3479
+ var json = this.jsonResponse;
3480
+ var usersConsole = json.usersConsole.userConsole;
3481
+ var usersMetadata = json.usersMetadata.userMetadata;
3482
+ var resourcesMetadata = json.resourcesMetadata.resourceMetadata;
3483
+ var wanted = request.sessions;
3484
+ var entries = usersConsole
3485
+ .filter(function (c) { return wanted.some(function (s) { return s.userId === c.userId && s.ip === c.terminalIP && s.start === new Date(c.startTime).getTime(); }); })
3486
+ .sort(function (a, b) { return new Date(a.startTime).getTime() - new Date(b.startTime).getTime(); });
3487
+ var teacherName = function (id) { return (usersMetadata.find(function (u) { return u.userId === id; }) || {}).fullName || ''; };
3488
+ var roomLabel = function (ip) { return _this.getRoomName(ip) || ip; };
3489
+ this.otherColumn = request.isUserWise ? 'Room' : 'Teacher';
3490
+ var rows = [];
3491
+ entries.forEach(function (c) {
3492
+ var start = new Date(c.startTime);
3493
+ var end = new Date(c.endTime);
3494
+ var head = {
3495
+ sessionHead: true,
3496
+ sessionTime: _this.formatClock(start) + " \u2013 " + _this.formatClock(end),
3497
+ sessionDuration: _this.formatTime(Math.round((end.getTime() - start.getTime()) / 1000)),
3498
+ sessionOther: request.isUserWise ? roomLabel(c.terminalIP) : teacherName(c.userId),
3499
+ };
3500
+ var sessionRows = _this.rowsForConsole(c, usersMetadata, resourcesMetadata);
3501
+ if (sessionRows.length === 0) {
3502
+ rows.push(Object.assign(Object.assign({}, head), { empty: true }));
3503
+ }
3504
+ else {
3505
+ sessionRows.forEach(function (r, i) { return rows.push(i === 0 ? Object.assign(Object.assign({}, r), head) : r); });
3506
+ }
3507
+ });
3508
+ this.currentTimeFrameUserArray = rows;
3509
+ // time per teacher (or per room), in order of first appearance
3510
+ var timeByOther = new Map();
3511
+ entries.forEach(function (c) {
3512
+ var key = request.isUserWise ? roomLabel(c.terminalIP) : teacherName(c.userId);
3513
+ if (!key)
3514
+ return;
3515
+ var ms = new Date(c.endTime).getTime() - new Date(c.startTime).getTime();
3516
+ timeByOther.set(key, (timeByOther.get(key) || 0) + Math.max(ms, 0));
3517
+ });
3518
+ this.people = Array.from(timeByOther.entries()).map(function (_g) {
3519
+ var _h = __read(_g, 2), label = _h[0], ms = _h[1];
3520
+ return ({ label: label, time: _this.formatTime(Math.round(ms / 1000)) });
3521
+ });
3522
+ this.peopleTitle = request.isUserWise ? 'Rooms used' : 'Teachers in this room';
3523
+ // a bare room number reads better as "Room 39"
3524
+ this.allTitle = !request.isUserWise && /^\d+$/.test(String(request.label).trim()) ? "Room " + request.label : request.label;
3525
+ this.allSummary = [
3526
+ { label: 'Sessions', value: "" + entries.length, icon: 'sessions', tone: 'lime' },
3527
+ // just the count here; the names are listed under the cards
3528
+ { label: request.isUserWise ? 'Rooms' : 'Teachers', value: "" + this.people.length, icon: request.isUserWise ? 'room' : 'teacher', tone: 'sand' },
3529
+ { label: 'First start', value: entries.length ? this.formatDate(new Date(entries[0].startTime)) : '—', icon: 'start', tone: 'sky' },
3530
+ {
3531
+ label: 'Last end',
3532
+ value: entries.length ? this.formatDate(new Date(Math.max.apply(Math, __spread(entries.map(function (c) { return new Date(c.endTime).getTime(); }))))) : '—',
3533
+ icon: 'end',
3534
+ tone: 'sky',
3535
+ },
3536
+ { label: 'Total time', value: request.total || '—', icon: 'clock', tone: 'mint' },
3537
+ ];
3538
+ };
3539
+ Object.defineProperty(SmallTableViewComponent.prototype, "sessionSummary", {
3540
+ // cards for a single session, read from the first row of that session
3541
+ get: function () {
3542
+ var first = this.currentTimeFrameUserArray[0] || {};
3543
+ return [
3544
+ { label: 'Subject', value: first.subject || '—', icon: 'subject', tone: 'lime' },
3545
+ { label: 'Room', value: first.roomName || first.room || '—', sub: first.roomName ? first.room : '', icon: 'room', tone: 'sand' },
3546
+ { label: 'Start', value: first.startTime || '—', icon: 'start', tone: 'sky' },
3547
+ { label: 'End', value: first.endtime || '—', icon: 'end', tone: 'sky' },
3548
+ { label: 'Total time', value: first.totalDuration || '—', icon: 'clock', tone: 'mint' },
3549
+ ];
3550
+ },
3551
+ enumerable: false,
3552
+ configurable: true
3553
+ });
3554
+ SmallTableViewComponent.prototype.formatClock = function (date) {
3555
+ var hours = date.getHours();
3556
+ return (hours % 12 || 12) + ":" + date.getMinutes().toString().padStart(2, '0') + " " + (hours >= 12 ? 'PM' : 'AM');
3557
+ };
3558
+ // one row per resource opened in a single console session
3559
+ SmallTableViewComponent.prototype.rowsForConsole = function (user, usersMetadata, resourcesMetadata) {
3560
+ var _this = this;
3561
+ var _a;
3562
+ var rows = [];
3563
+ var userDetails = usersMetadata.find(function (userMeta) { return userMeta.userId === user.userId; });
3564
+ if (user === null || user === void 0 ? void 0 : user.userConsoleResources) {
3565
+ var userConsoleResource = (_a = user.userConsoleResources) === null || _a === void 0 ? void 0 : _a.userConsoleResource;
3566
+ if (userConsoleResource && userConsoleResource.length > 0) {
3567
+ userConsoleResource.forEach(function (userResource, userIndex) {
3568
+ var _a, _b, _c, _d, _e, _f;
3569
+ var resourceId = userResource.resourceId;
3570
+ var resourceDetails = resourcesMetadata.find(function (resource) { return resource.resourceId === resourceId &&
3571
+ userResource.type === resource.type; });
3572
+ var prefix = _this.getDisplayTypeValue(userResource.type);
3573
+ var title = (resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.title) || '';
3574
+ var tp = _this.decodeHtmlEntities((_a = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.teachingPoint) === null || _a === void 0 ? void 0 : _a.title);
3575
+ var subject = _this.decodeHtmlEntities((_b = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.subject) === null || _b === void 0 ? void 0 : _b.title);
3576
+ var chapter = _this.decodeHtmlEntities((_c = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.chapter) === null || _c === void 0 ? void 0 : _c.title);
3577
+ var grade = _this.decodeHtmlEntities((_d = resourceDetails === null || resourceDetails === void 0 ? void 0 : resourceDetails.grade) === null || _d === void 0 ? void 0 : _d.title);
3578
+ var startTime = new Date(user === null || user === void 0 ? void 0 : user.startTime);
3579
+ var endTime = new Date(user === null || user === void 0 ? void 0 : user.endTime);
3580
+ var totalDuration = _this.formatTime(Math.round((endTime.getTime() - startTime.getTime()) / 1000));
3581
+ var duration = parseInt((userResource === null || userResource === void 0 ? void 0 : userResource.duration) || '0');
3582
+ //duration = duration === 0 ? "<span class='dddd'>(Seen)</span>" : this.formatTime(duration);
3583
+ if (((_e = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _e === void 0 ? void 0 : _e.includes('question')) || ((_f = userResource === null || userResource === void 0 ? void 0 : userResource.type) === null || _f === void 0 ? void 0 : _f.includes('cquestion'))) {
3584
+ duration = 0;
3585
+ }
3586
+ else {
3587
+ duration = duration === 0 ? "<span class='dddd'>(Seen)</span>" : _this.formatTime(duration);
3588
+ }
3589
+ var finalAssetTitle = prefix + " : " + title + " : " + duration;
3590
+ if (title === '') {
3591
+ finalAssetTitle = prefix + " : " + duration;
3592
+ }
3593
+ rows.push({
3594
+ userId: user.userId,
3595
+ teacherName: userIndex === 0 ? userDetails === null || userDetails === void 0 ? void 0 : userDetails.fullName : '',
3596
+ subjects: userIndex === 0 ? userDetails === null || userDetails === void 0 ? void 0 : userDetails.userSubjects : '',
3597
+ date: userIndex === 0 ? user === null || user === void 0 ? void 0 : user.selectedDate : '',
3598
+ startTime: userIndex === 0 ? _this.formatDate(startTime) : _this.formatDate(startTime),
3599
+ endtime: userIndex === 0 ? _this.formatDate(endTime) : _this.formatDate(endTime),
3600
+ // kept on every row: the popup reads it from whichever row comes first
3601
+ totalDuration: totalDuration,
3602
+ room: userIndex === 0 ? user === null || user === void 0 ? void 0 : user.terminalIP : '',
3603
+ roomName: userIndex === 0 ? _this.getRoomName(user === null || user === void 0 ? void 0 : user.terminalIP) : '',
3604
+ tp: tp,
3605
+ subject: subject,
3606
+ chapter: chapter,
3607
+ grade: grade,
3608
+ details: finalAssetTitle,
3609
+ ip: user === null || user === void 0 ? void 0 : user.terminalIP,
3610
+ sessionStart: startTime.getTime(),
3611
+ });
3612
+ });
3613
+ }
3614
+ }
3615
+ return rows;
3616
+ };
3238
3617
  SmallTableViewComponent.prototype.filterDataByCriteria = function (userData, inputUserId, inputStartTime, inputEndTime) {
3239
3618
  var parseTime = function (timeString) {
3240
3619
  if (!timeString)
@@ -3332,6 +3711,23 @@
3332
3711
  ? this.allUsers
3333
3712
  : this.currentUserArray);
3334
3713
  };
3714
+ // room name from schoolTerminals metadata, '' when not set
3715
+ SmallTableViewComponent.prototype.getRoomName = function (ip) {
3716
+ var _a, _b, _c;
3717
+ var terminals = ((_b = (_a = this.jsonResponse) === null || _a === void 0 ? void 0 : _a.schoolTerminals) === null || _b === void 0 ? void 0 : _b.schoolTerminal) || [];
3718
+ var terminal = terminals.find(function (t) { return t.terminalKey === ip; });
3719
+ try {
3720
+ return (terminal === null || terminal === void 0 ? void 0 : terminal.metadata) ? (((_c = JSON.parse(terminal.metadata)) === null || _c === void 0 ? void 0 : _c.location) || '').trim() : '';
3721
+ }
3722
+ catch (e) {
3723
+ return '';
3724
+ }
3725
+ };
3726
+ SmallTableViewComponent.prototype.onEscape = function () {
3727
+ if (this.isDisplay) {
3728
+ this.onCloseBtnClick();
3729
+ }
3730
+ };
3335
3731
  SmallTableViewComponent.prototype.onCloseBtnClick = function () {
3336
3732
  this.isDisplay = false;
3337
3733
  //this.pdService.openSmallTable.next(false);
@@ -3341,15 +3737,769 @@
3341
3737
  SmallTableViewComponent.decorators = [
3342
3738
  { type: i0.Component, args: [{
3343
3739
  selector: 'lib-small-table-view',
3344
- template: "<div id=\"pd-deatilViewContainer\" *ngIf=\"isDisplay\">\n <div class=\"pd-detailview-header\">\n <div class=\"pd-detailview-title\">Details</div>\n <div id=\"pd-closeButton\" (click)=\"onCloseBtnClick()\">+</div>\n </div>\n\n <div class=\"newTablesContainer\">\n\n <table id=\"userDataTable\" class=\"table table-bordered\">\n <tbody>\n <tr>\n <th style=\"font-weight: bold;\"><label>Name:</label></th>\n <td><label>{{ currentTimeFrameUserArray[0]?.teacherName || '' }}</label></td>\n <th style=\"font-weight: bold;\"><label>Start Time:</label></th>\n <td><label>{{ currentTimeFrameUserArray[0]?.startTime || '' }}</label></td>\n </tr>\n <tr>\n <th style=\"font-weight: bold;\"><label>Subject:</label></th>\n <td><label>{{ currentTimeFrameUserArray[0]?.subject || '' }}</label></td>\n <th style=\"font-weight: bold;\"><label>End Time:</label></th>\n <td><label>{{ currentTimeFrameUserArray[0]?.endtime || '' }}</label></td>\n </tr>\n <tr>\n <th style=\"font-weight: bold;\"><label>Room Details:</label></th>\n <td><label> {{ currentTimeFrameUserArray[0]?.room || '' }}</label></td>\n </tr>\n </tbody>\n </table>\n \n \n\n <table class=\"table table-bordered\" id=\"resoureMetadataTable\">\n <tbody>\n <tr>\n <th style=\"font-weight: bold;\"><label>Subject</label></th>\n <th style=\"font-weight: bold;\"><label>Class</label></th>\n <th style=\"font-weight: bold;\"><label>Chapter Name</label></th>\n <th style=\"font-weight: bold;\"><label>Topic Name</label></th>\n <th style=\"font-weight: bold;\"><label>Assets Details</label></th>\n </tr>\n <tr *ngFor=\"let item of currentTimeFrameUserArray\">\n <td><label>{{ item.subject || '' }}</label></td>\n <td><label>{{ item.grade || '' }}</label></td>\n <td><label>{{ item.chapter || '' }}</label></td>\n <td [innerHTML]=\"item.tp || ''\"></td> <!-- Use innerHTML to handle HTML content -->\n <td [innerHTML]=\"item.details || ''\"></td> <!-- Assets Details column -->\n </tr>\n </tbody>\n </table>\n\n \n </div> \n </div>",
3345
- styles: ["#pd-deatilViewContainer{position:absolute;background:#fff;box-shadow:0 0 17px #818181;font-size:12px;width:50%;max-height:350px;top:140px;left:230px}#pd-deatilViewContainer .pd-detailview-header{position:relative;width:100%;height:35px;background-color:#e6e6e6}#pd-deatilViewContainer .pd-detailview-title{position:absolute;margin-left:5px;font-size:20px;line-height:35px}#pd-deatilViewContainer #pd-closeButton{width:24px;position:absolute;float:right;right:0;cursor:pointer;height:24px;background:#000;border-radius:12px;color:#fff;line-height:22px;text-align:center;font-size:26px;transform:rotate(45deg);margin:5px}#pd-deatilViewContainer #pd-deatilViewTable{overflow:scroll;scroll-behavior:smooth;height:auto;margin:5px;max-height:300px}.newTablesContainer{overflow:scroll;height:310px;padding:3px;background:#fff;margin-bottom:2px}.dddd{color:#00f!important}"]
3740
+ template: "<div class=\"pd-detail-backdrop\" *ngIf=\"isDisplay\" (click)=\"onCloseBtnClick()\">\n <div id=\"pd-deatilViewContainer\" class=\"pd-detail-dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"pd-detail-title\" (click)=\"$event.stopPropagation()\">\n <div class=\"pd-detail-header\">\n <div class=\"pd-detail-heading\">\n <span class=\"pd-detail-swatch\" *ngIf=\"accentColor\" [style.background-color]=\"accentColor\"></span>\n <div>\n <div id=\"pd-detail-title\" class=\"pd-detail-title\">{{ (mode === 'all' ? allTitle : currentTimeFrameUserArray[0]?.teacherName) || 'Details' }}</div>\n <div class=\"pd-detail-subtitle\">{{ mode === 'all' ? 'All Sessions Today' : 'Session Details' }}</div>\n </div>\n </div>\n <button type=\"button\" class=\"pd-detail-close\" aria-label=\"Close\" (click)=\"onCloseBtnClick()\">&times;</button>\n </div>\n\n <div class=\"pd-detail-summary\">\n <div class=\"pd-detail-card\" *ngFor=\"let card of (mode === 'all' ? allSummary : sessionSummary)\" [ngClass]=\"'pd-tone-' + card.tone\">\n <div class=\"pd-detail-card-label\">\n <span class=\"pd-card-icon\" [ngSwitch]=\"card.icon\" aria-hidden=\"true\">\n <svg *ngSwitchCase=\"'sessions'\" viewBox=\"0 0 24 24\"><path d=\"M4 6h16M4 12h16M4 18h10\" /></svg>\n <svg *ngSwitchCase=\"'room'\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" /><path d=\"M12 16v4M8 20h8\" /></svg>\n <svg *ngSwitchCase=\"'teacher'\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"8\" r=\"4\" /><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\" /></svg>\n <svg *ngSwitchCase=\"'subject'\" viewBox=\"0 0 24 24\"><path d=\"M4 5a2 2 0 0 1 2-2h13v16H6a2 2 0 0 0-2 2V5z\" /><path d=\"M4 19a2 2 0 0 1 2-2h13\" /></svg>\n <svg *ngSwitchCase=\"'start'\" viewBox=\"0 0 24 24\"><path d=\"M14 4h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4\" /><path d=\"M4 12h11M11 8l4 4-4 4\" /></svg>\n <svg *ngSwitchCase=\"'end'\" viewBox=\"0 0 24 24\"><path d=\"M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4\" /><path d=\"M9 12h11M16 8l4 4-4 4\" /></svg>\n <svg *ngSwitchCase=\"'clock'\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"9\" /><path d=\"M12 7v5l3 2\" /></svg>\n </span>\n {{ card.label }}\n </div>\n <div class=\"pd-detail-card-value\">\n {{ card.value }}\n <span class=\"pd-detail-muted\" *ngIf=\"card.sub\">{{ card.sub }}</span>\n </div>\n </div>\n </div>\n\n <div class=\"pd-people\" *ngIf=\"mode === 'all' && people.length\">\n <span class=\"pd-people-title\">{{ peopleTitle }}</span>\n <span class=\"pd-person\" *ngFor=\"let p of people\">{{ p.label }} <span class=\"pd-person-time\">{{ p.time }}</span></span>\n </div>\n\n <div class=\"pd-detail-body\">\n <div class=\"pd-detail-empty\" *ngIf=\"currentTimeFrameUserArray.length === 0\">No content was opened in this session.</div>\n <table class=\"pd-detail-table\" *ngIf=\"currentTimeFrameUserArray.length > 0\">\n <thead>\n <tr>\n <ng-container *ngIf=\"mode === 'all'\">\n <th>Time</th>\n <th>{{ otherColumn }}</th>\n </ng-container>\n <th>Subject</th>\n <th>Class</th>\n <th>Chapter</th>\n <th>Topic</th>\n <th>Asset details</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let item of currentTimeFrameUserArray\" [class.pd-session-start]=\"mode === 'all' && item.sessionHead\">\n <ng-container *ngIf=\"mode === 'all'\">\n <td class=\"pd-nowrap\">\n <ng-container *ngIf=\"item.sessionHead\">\n {{ item.sessionTime }}\n <div class=\"pd-detail-muted\">{{ item.sessionDuration }}</div>\n </ng-container>\n </td>\n <td>{{ item.sessionHead ? item.sessionOther : '' }}</td>\n </ng-container>\n <ng-container *ngIf=\"!item.empty\">\n <td>{{ item.subject || '\u2014' }}</td>\n <td class=\"pd-nowrap\">{{ item.grade || '\u2014' }}</td>\n <td>{{ item.chapter || '\u2014' }}</td>\n <td [class.pd-detail-muted]=\"!item.tp\">{{ item.tp || '\u2014' }}</td>\n <td [innerHTML]=\"item.details || ''\"></td>\n </ng-container>\n <td *ngIf=\"item.empty\" colspan=\"5\" class=\"pd-detail-muted\">No content was opened in this session.</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>\n",
3741
+ styles: [".pd-detail-backdrop{position:fixed;inset:0;top:0;left:0;right:0;bottom:0;z-index:1000;background:rgba(17,24,39,.45);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box}.pd-detail-dialog{background:#fff;border-radius:12px;box-shadow:0 20px 50px rgba(0,0,0,.25);width:900px;max-width:100%;height:600px;max-height:85vh;display:flex;flex-direction:column;overflow:hidden;font-family:Arial,sans-serif;font-size:13px;color:#1f2937}.pd-detail-header{flex:0 0 auto;display:flex;align-items:flex-start;justify-content:space-between;padding:18px 20px 12px;border-bottom:1px solid #e5e7eb}.pd-detail-title{font-size:20px;font-weight:700;line-height:1.3;color:#1f2937}.pd-detail-heading{display:flex;align-items:flex-start;gap:12px}.pd-detail-swatch{flex:0 0 auto;width:14px;height:14px;border-radius:50%;margin-top:6px}.pd-detail-subtitle{font-size:15px;font-weight:700;color:#c9952b;margin-top:4px}.pd-detail-close{border:none;background:#f3f4f6;color:#374151;width:32px;height:32px;border-radius:50%;font-size:22px;line-height:30px;cursor:pointer;flex-shrink:0}.pd-detail-close:hover{background:#e5e7eb}.pd-detail-summary{flex:0 0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;padding:14px 20px}.pd-detail-card{background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;padding:12px 14px;text-align:center}.pd-detail-card.pd-tone-lime{background:#eef6a3;border-color:#dfe98a}.pd-detail-card.pd-tone-sand{background:#f6ecd9;border-color:#ebdcc0}.pd-detail-card.pd-tone-sky{background:#e1edfb;border-color:#cbdcf3}.pd-detail-card.pd-tone-mint{background:#f3fbe6;border-color:#dfeecb}.pd-detail-card-label{display:flex;align-items:center;justify-content:center;gap:6px;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#6b7280;margin-bottom:6px}.pd-card-icon{display:inline-flex}.pd-card-icon svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.pd-detail-card-value{font-size:15px;font-weight:700;word-break:break-word}.pd-people{flex:0 0 auto;display:flex;flex-wrap:wrap;align-items:center;gap:6px;max-height:72px;overflow:auto;padding:0 20px 12px}.pd-people-title{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#6b7280;margin-right:4px}.pd-person{padding:4px 10px;border-radius:999px;background:#f6ecd9;border:1px solid #ebdcc0;font-size:12px;font-weight:700;color:#1f2937}.pd-person-time{font-weight:400;color:#6b7280;margin-left:4px}.pd-detail-muted{color:#9ca3af;font-weight:400}.pd-detail-body{overflow:auto;margin:0 20px 20px;flex:1 1 auto;min-height:0;border:1px solid #e5e7eb;border-radius:8px}.pd-detail-table{width:100%;border-collapse:separate;border-spacing:0}.pd-detail-table td,.pd-detail-table th{text-align:left;padding:9px 12px;border-bottom:1px solid #f0f0f0;vertical-align:top}.pd-detail-table th{position:sticky;top:0;z-index:1;background:#f3f4f6;font-size:12px;color:#4b5563;font-weight:700}.pd-detail-table tbody tr:nth-child(2n) td{background:#fafafa}.pd-detail-table tbody tr.pd-session-start:not(:first-child) td{border-top:2px solid #e5e7eb}.pd-detail-table tbody tr:last-child td{border-bottom:none}.pd-detail-table tbody tr:hover td{background:#eef2ff}.pd-nowrap{white-space:nowrap}.pd-detail-empty{padding:24px;text-align:center;color:#6b7280}:host ::ng-deep .dddd{color:#2563eb;font-style:italic}"]
3346
3742
  },] }
3347
3743
  ];
3348
3744
  SmallTableViewComponent.ctorParameters = function () { return [
3349
3745
  { type: PrincipalDashboardLibV2Service }
3350
3746
  ]; };
3351
3747
  SmallTableViewComponent.propDecorators = {
3352
- data: [{ type: i0.Input }]
3748
+ data: [{ type: i0.Input }],
3749
+ onEscape: [{ type: i0.HostListener, args: ['document:keydown.escape',] }]
3750
+ };
3751
+
3752
+ var TimelineComponent = /** @class */ (function () {
3753
+ function TimelineComponent() {
3754
+ this.rows = [];
3755
+ this.labelTitle = 'Name';
3756
+ this.barClick = new i0.EventEmitter();
3757
+ // clicking a row's total asks for every session in that row
3758
+ this.totalClick = new i0.EventEmitter();
3759
+ this.renderedRows = [];
3760
+ this.ticks = [];
3761
+ // :15 and :45 marks, drawn in the header only, without labels
3762
+ this.quarterTicks = [];
3763
+ // fixed width per hour so a full day scrolls horizontally instead of squeezing
3764
+ this.hourWidth = 160;
3765
+ this.minWidth = 0;
3766
+ this.nowLeft = null;
3767
+ this.sortKey = 'default';
3768
+ this.sortAsc = true;
3769
+ this.tooltip = null;
3770
+ this.rangeStart = 0;
3771
+ this.rangeEnd = 0;
3772
+ }
3773
+ TimelineComponent.prototype.ngOnChanges = function () {
3774
+ this.render();
3775
+ };
3776
+ TimelineComponent.prototype.render = function () {
3777
+ var _this = this;
3778
+ var rows = this.rows || [];
3779
+ var allBars = rows.reduce(function (acc, row) { return acc.concat(row.bars); }, []);
3780
+ if (allBars.length === 0) {
3781
+ this.ticks = [];
3782
+ this.quarterTicks = [];
3783
+ this.nowLeft = null;
3784
+ this.minWidth = 0;
3785
+ this.renderedRows = rows.map(function (row, i) { return _this.toRenderedRow(row, i); });
3786
+ this.applySort();
3787
+ return;
3788
+ }
3789
+ var minStart = Math.min.apply(Math, __spread(allBars.map(function (b) { return b.start.getTime(); })));
3790
+ var maxEnd = Math.max.apply(Math, __spread(allBars.map(function (b) { return b.end.getTime(); })));
3791
+ var hour = 3600 * 1000;
3792
+ var startDate = new Date(minStart);
3793
+ startDate.setMinutes(0, 0, 0);
3794
+ this.rangeStart = startDate.getTime();
3795
+ this.rangeEnd = Math.max(Math.ceil((maxEnd - this.rangeStart) / hour) * hour + this.rangeStart, this.rangeStart + hour);
3796
+ var spanHours = (this.rangeEnd - this.rangeStart) / hour;
3797
+ // label (170) + total (90) + track margins (48) + hours
3798
+ this.minWidth = 170 + 90 + 48 + spanHours * this.hourWidth;
3799
+ this.ticks = [];
3800
+ this.quarterTicks = [];
3801
+ for (var t = this.rangeStart; t <= this.rangeEnd; t += hour / 4) {
3802
+ var minutes = new Date(t).getMinutes();
3803
+ if (minutes === 15 || minutes === 45) {
3804
+ this.quarterTicks.push(this.toPercent(t));
3805
+ }
3806
+ else {
3807
+ this.ticks.push({ left: this.toPercent(t), label: this.formatClock(new Date(t)), major: minutes === 0 });
3808
+ }
3809
+ }
3810
+ var now = Date.now();
3811
+ this.nowLeft = now >= this.rangeStart && now <= this.rangeEnd ? this.toPercent(now) : null;
3812
+ this.renderedRows = rows.map(function (row, i) { return _this.toRenderedRow(row, i); });
3813
+ this.applySort();
3814
+ };
3815
+ TimelineComponent.prototype.toggleSort = function (key) {
3816
+ if (this.sortKey === key) {
3817
+ if (this.sortAsc) {
3818
+ this.sortAsc = false;
3819
+ }
3820
+ else {
3821
+ this.sortKey = 'default';
3822
+ this.sortAsc = true;
3823
+ }
3824
+ }
3825
+ else {
3826
+ this.sortKey = key;
3827
+ this.sortAsc = key === 'name';
3828
+ }
3829
+ this.applySort();
3830
+ };
3831
+ TimelineComponent.prototype.sortIcon = function (key) {
3832
+ if (this.sortKey !== key)
3833
+ return '';
3834
+ return this.sortAsc ? '▲' : '▼';
3835
+ };
3836
+ TimelineComponent.prototype.onBarClick = function (bar) {
3837
+ this.tooltip = null;
3838
+ this.barClick.emit({ index: bar.index, color: bar.color });
3839
+ };
3840
+ TimelineComponent.prototype.onTotalClick = function (row) {
3841
+ if (!row.bars.length)
3842
+ return;
3843
+ this.tooltip = null;
3844
+ this.totalClick.emit({ label: row.label, indexes: row.bars.map(function (b) { return b.index; }), color: row.color, total: row.total });
3845
+ };
3846
+ TimelineComponent.prototype.showTooltip = function (event, bar) {
3847
+ this.tooltip = { text: bar.tooltip, x: event.clientX + 12, y: event.clientY + 12 };
3848
+ };
3849
+ TimelineComponent.prototype.hideTooltip = function () {
3850
+ this.tooltip = null;
3851
+ };
3852
+ TimelineComponent.prototype.trackByLabel = function (index, row) {
3853
+ return row.label;
3854
+ };
3855
+ TimelineComponent.prototype.toRenderedRow = function (row, i) {
3856
+ var _this = this;
3857
+ var totalMs = this.mergedDuration(row.bars);
3858
+ var starts = row.bars.map(function (b) { return b.start.getTime(); });
3859
+ var ends = row.bars.map(function (b) { return b.end.getTime(); });
3860
+ var spanLeft = starts.length ? this.toPercent(Math.min.apply(Math, __spread(starts))) : 0;
3861
+ var spanWidth = ends.length ? this.toPercent(Math.max.apply(Math, __spread(ends))) - spanLeft : 0;
3862
+ return {
3863
+ spanLeft: spanLeft,
3864
+ spanWidth: spanWidth,
3865
+ label: row.label,
3866
+ color: this.rowColor(i),
3867
+ totalMs: totalMs,
3868
+ total: this.formatDuration(totalMs),
3869
+ order: i,
3870
+ bars: row.bars.map(function (bar) {
3871
+ var left = _this.toPercent(bar.start.getTime());
3872
+ return Object.assign(Object.assign({}, bar), { left: left, width: Math.max(_this.toPercent(bar.end.getTime()) - left, 0), color: _this.rowColor(i) });
3873
+ }),
3874
+ };
3875
+ };
3876
+ // unique soft colour per row: golden-angle hue steps never repeat and keep neighbouring rows apart
3877
+ TimelineComponent.prototype.rowColor = function (index) {
3878
+ var hue = Math.round((217 + index * 137.508) % 360);
3879
+ return "hsl(" + hue + ", 62%, 68%)";
3880
+ };
3881
+ TimelineComponent.prototype.applySort = function () {
3882
+ var _this = this;
3883
+ var dir = this.sortAsc ? 1 : -1;
3884
+ this.renderedRows = __spread(this.renderedRows).sort(function (a, b) {
3885
+ if (_this.sortKey === 'name') {
3886
+ return dir * a.label.localeCompare(b.label, undefined, { numeric: true });
3887
+ }
3888
+ if (_this.sortKey === 'total') {
3889
+ return dir * (a.totalMs - b.totalMs);
3890
+ }
3891
+ return a.order - b.order;
3892
+ });
3893
+ };
3894
+ // overlapping sessions in the same row are counted once
3895
+ TimelineComponent.prototype.mergedDuration = function (bars) {
3896
+ var sorted = __spread(bars).sort(function (a, b) { return a.start.getTime() - b.start.getTime(); });
3897
+ var total = 0;
3898
+ var curStart = null;
3899
+ var curEnd = null;
3900
+ sorted.forEach(function (bar) {
3901
+ var s = bar.start.getTime();
3902
+ var e = bar.end.getTime();
3903
+ if (curEnd === null || s > curEnd) {
3904
+ if (curEnd !== null)
3905
+ total += curEnd - curStart;
3906
+ curStart = s;
3907
+ curEnd = e;
3908
+ }
3909
+ else if (e > curEnd) {
3910
+ curEnd = e;
3911
+ }
3912
+ });
3913
+ if (curEnd !== null)
3914
+ total += curEnd - curStart;
3915
+ return total;
3916
+ };
3917
+ TimelineComponent.prototype.toPercent = function (time) {
3918
+ var span = this.rangeEnd - this.rangeStart;
3919
+ return span > 0 ? ((time - this.rangeStart) / span) * 100 : 0;
3920
+ };
3921
+ TimelineComponent.prototype.formatClock = function (date) {
3922
+ var hours = date.getHours();
3923
+ var minutes = date.getMinutes();
3924
+ var ampm = hours >= 12 ? 'PM' : 'AM';
3925
+ hours = hours % 12 || 12;
3926
+ return minutes === 0 ? hours + " " + ampm : hours + ":" + minutes.toString().padStart(2, '0');
3927
+ };
3928
+ TimelineComponent.prototype.formatDuration = function (ms) {
3929
+ var totalMinutes = Math.round(ms / 60000);
3930
+ var hours = Math.floor(totalMinutes / 60);
3931
+ var minutes = totalMinutes % 60;
3932
+ return hours + ":" + minutes.toString().padStart(2, '0') + " hrs";
3933
+ };
3934
+ return TimelineComponent;
3935
+ }());
3936
+ TimelineComponent.decorators = [
3937
+ { type: i0.Component, args: [{
3938
+ selector: 'lib-timeline',
3939
+ template: "<div class=\"tl-container\" [style.min-width.px]=\"minWidth\">\n <div class=\"tl-row tl-header\">\n <div class=\"tl-label tl-sortable\" (click)=\"toggleSort('name')\">{{ labelTitle }} {{ sortIcon('name') }}</div>\n <div class=\"tl-track tl-axis\">\n <div class=\"tl-mark tl-mark-quarter\" *ngFor=\"let left of quarterTicks\" [style.left.%]=\"left\"></div>\n <div class=\"tl-mark\" *ngFor=\"let tick of ticks\" [class.tl-mark-hour]=\"tick.major\" [style.left.%]=\"tick.left\"></div>\n <div class=\"tl-tick\" *ngFor=\"let tick of ticks\" [class.tl-minor]=\"!tick.major\" [style.left.%]=\"tick.left\">{{ tick.label }}</div>\n </div>\n <div class=\"tl-total tl-sortable\" (click)=\"toggleSort('total')\">Total {{ sortIcon('total') }}</div>\n </div>\n\n <div class=\"tl-empty\" *ngIf=\"renderedRows.length === 0\">No usage data for this view.</div>\n\n <div class=\"tl-row\" *ngFor=\"let row of renderedRows; trackBy: trackByLabel; let odd = odd\" [class.tl-odd]=\"odd\">\n <div class=\"tl-label\" [title]=\"row.label\">{{ row.label }}</div>\n <div class=\"tl-track\">\n <div class=\"tl-gridline\" *ngFor=\"let tick of ticks\" [class.tl-minor]=\"!tick.major\" [style.left.%]=\"tick.left\"></div>\n <div class=\"tl-span\" *ngIf=\"row.bars.length\" [style.left.%]=\"row.spanLeft\" [style.width.%]=\"row.spanWidth\"></div>\n <div class=\"tl-now\" *ngIf=\"nowLeft !== null\" [style.left.%]=\"nowLeft\"></div>\n <div\n class=\"tl-bar\"\n *ngFor=\"let bar of row.bars\"\n [style.left.%]=\"bar.left\"\n [style.width.%]=\"bar.width\"\n [style.background-color]=\"bar.color\"\n (click)=\"onBarClick(bar)\"\n (mousemove)=\"showTooltip($event, bar)\"\n (mouseleave)=\"hideTooltip()\"\n ></div>\n </div>\n <div\n class=\"tl-total\"\n [class.tl-total-link]=\"row.bars.length\"\n [title]=\"row.bars.length ? 'Show all sessions' : ''\"\n (click)=\"onTotalClick(row)\"\n >{{ row.bars.length ? row.total : '\u2014' }}</div>\n </div>\n\n <div class=\"tl-tooltip\" *ngIf=\"tooltip\" [style.left.px]=\"tooltip.x\" [style.top.px]=\"tooltip.y\">{{ tooltip.text }}</div>\n</div>\n",
3940
+ styles: [".tl-container{font-family:Arial,sans-serif;font-size:13px;color:#1f2937;border:1px solid #e5e7eb;border-radius:8px;overflow:clip;min-width:900px;box-sizing:border-box;position:relative;isolation:isolate}.tl-row{display:flex;height:30px;border-bottom:1px solid #e2e6ec;background:#fff}.tl-row:hover:not(.tl-header){background:#eef2ff}.tl-odd{background:#f4f6f9}.tl-header{position:sticky;top:0;z-index:3;height:32px;background:#f8fafc;font-weight:700;color:#475569;border-bottom:1px solid #e5e7eb}.tl-label{flex:0 0 170px;overflow:hidden;text-overflow:ellipsis;border-right:1px solid #e5e7eb;left:0}.tl-label,.tl-total{padding:0 8px;line-height:30px;text-align:right;white-space:nowrap;position:sticky;z-index:2;background:inherit}.tl-total{flex:0 0 90px;font-variant-numeric:tabular-nums;color:#334155;border-left:1px solid #e5e7eb;right:0}.tl-total-link{cursor:pointer;color:#4338ca}.tl-total-link:hover{text-decoration:underline}.tl-sortable{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;line-height:32px}.tl-track{flex:1 1 auto;position:relative;margin:0 24px}.tl-axis .tl-tick{position:absolute;top:0;line-height:24px;transform:translateX(-50%);font-weight:400;font-size:11px;color:#64748b;white-space:nowrap}.tl-axis .tl-tick.tl-minor{font-size:10px;color:#94a3b8}.tl-axis .tl-mark{position:absolute;bottom:0;height:8px;border-left:2px solid #7c9cf5;transform:translateX(-1px)}.tl-axis .tl-mark.tl-mark-hour{height:10px;border-left:2px solid #475569;transform:translateX(-1px)}.tl-axis .tl-mark.tl-mark-quarter{height:8px;border-left:1px solid #c3cad6;transform:translateX(-.5px)}.tl-gridline{position:absolute;top:0;bottom:0;border-left:1px solid #d3d2d2;z-index:3}.tl-gridline.tl-minor{border-left:1px dashed #d3d2d2}.tl-span{position:absolute;top:5px;bottom:5px;background:#e4e8ee;border-radius:2px}.tl-now{top:0;bottom:0;border-left:2px solid #f43f5e}.tl-bar,.tl-now{position:absolute;z-index:1}.tl-bar{top:5px;bottom:5px;min-width:4px;border-radius:2px;cursor:pointer;box-shadow:inset 1px 0 0 hsla(0,0%,100%,.45);transition:filter .12s}.tl-bar:hover{filter:brightness(.82);z-index:2;box-shadow:0 2px 6px rgba(79,70,229,.35)}.tl-empty{padding:16px;text-align:center;color:#666}.tl-tooltip{position:fixed;z-index:1000;background:#1e293b;color:#fff;font-size:12px;padding:6px 10px;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.2);white-space:pre-line;pointer-events:none;max-width:320px}"]
3941
+ },] }
3942
+ ];
3943
+ TimelineComponent.propDecorators = {
3944
+ rows: [{ type: i0.Input }],
3945
+ labelTitle: [{ type: i0.Input }],
3946
+ barClick: [{ type: i0.Output }],
3947
+ totalClick: [{ type: i0.Output }]
3948
+ };
3949
+
3950
+ var MINUTE = 60 * 1000;
3951
+ var HOUR = 60 * MINUTE;
3952
+ var SHORT_SESSION_MIN = 5;
3953
+ // width the page is laid out at for the PDF, so the export looks the same on any screen
3954
+ var PDF_RENDER_WIDTH = 1280;
3955
+ var InsightsComponent = /** @class */ (function () {
3956
+ function InsightsComponent(pdService, host) {
3957
+ this.pdService = pdService;
3958
+ this.host = host;
3959
+ this.schoolName = '';
3960
+ this.dayLabel = '';
3961
+ this.kpis = [];
3962
+ this.hourly = [];
3963
+ this.grades = [];
3964
+ this.topTeachers = [];
3965
+ // donut of subject share: each segment is a dash along one circle
3966
+ this.subjectDonut = { total: '', segments: [] };
3967
+ this.donutRadius = 52;
3968
+ // columns per class, in class order
3969
+ this.gradeColumns = [];
3970
+ // one tile per room, shaded by share of the school day in use
3971
+ this.roomTiles = [];
3972
+ this.idleRooms = [];
3973
+ this.inactiveTeachers = [];
3974
+ this.contentMix = [];
3975
+ this.chapters = [];
3976
+ this.attention = [];
3977
+ this.hasSessions = false;
3978
+ this.isExporting = false;
3979
+ // accordion: the full follow-up lists open below the summary tiles
3980
+ this.detailsOpen = false;
3981
+ this.eligibleCount = 0;
3982
+ this.classroomCount = 0;
3983
+ this.fileDate = '';
3984
+ // ---------- count-up for the headline numbers
3985
+ this.countFrame = 0;
3986
+ }
3987
+ Object.defineProperty(InsightsComponent.prototype, "data", {
3988
+ set: function (res) {
3989
+ if (res) {
3990
+ this.build(typeof res === 'string' ? JSON.parse(res) : res);
3991
+ }
3992
+ },
3993
+ enumerable: false,
3994
+ configurable: true
3995
+ });
3996
+ InsightsComponent.prototype.ngAfterViewInit = function () {
3997
+ var _this = this;
3998
+ setTimeout(function () { return _this.fitHeight(); });
3999
+ };
4000
+ // fill from where the page starts down to the bottom of the window and scroll inside it
4001
+ InsightsComponent.prototype.fitHeight = function () {
4002
+ var page = this.host.nativeElement.querySelector('.in-page');
4003
+ if (!page)
4004
+ return;
4005
+ var top = page.getBoundingClientRect().top;
4006
+ page.style.height = Math.max(window.innerHeight - top, 240) + 'px';
4007
+ };
4008
+ InsightsComponent.prototype.build = function (json) {
4009
+ var _this = this;
4010
+ var users = this.list(json, 'usersMetadata', 'userMetadata');
4011
+ var consoles = this.list(json, 'usersConsole', 'userConsole');
4012
+ var resources = this.list(json, 'resourcesMetadata', 'resourceMetadata');
4013
+ var terminals = this.list(json, 'schoolTerminals', 'schoolTerminal');
4014
+ this.schoolName = (json.schoolMetadata && json.schoolMetadata.school && json.schoolMetadata.school.schoolName) || '';
4015
+ var userById = new Map();
4016
+ users.forEach(function (u) { return userById.set(u.userId, u); });
4017
+ var resourceById = new Map();
4018
+ resources.forEach(function (r) { return resourceById.set(r.resourceId, r); });
4019
+ var roomByIp = new Map();
4020
+ terminals.forEach(function (t) { return roomByIp.set(t.terminalKey, _this.pdService.getRoomLabel(_this.location(t.metadata), t.terminalKey)); });
4021
+ var classrooms = terminals.filter(function (t) { return t.terminalType === 'classroom'; });
4022
+ var sessions = consoles
4023
+ .map(function (c) { return ({
4024
+ userId: c.userId,
4025
+ ip: c.terminalIP,
4026
+ start: _this.parseTime(c.startTime),
4027
+ end: _this.parseTime(c.endTime),
4028
+ resources: ((c.userConsoleResources && c.userConsoleResources.userConsoleResource) || []).map(function (r) { return ({
4029
+ resourceId: r.resourceId,
4030
+ type: r.type,
4031
+ }); }),
4032
+ }); })
4033
+ .filter(function (s) { return !isNaN(s.start) && !isNaN(s.end) && s.end >= s.start; });
4034
+ // zero-length entries are logins with no class time; keep them out of time-based figures
4035
+ var timed = sessions.filter(function (s) { return s.end > s.start; });
4036
+ this.hasSessions = sessions.length > 0;
4037
+ var first = sessions.length ? new Date(Math.min.apply(Math, __spread(sessions.map(function (s) { return s.start; })))) : null;
4038
+ this.fileDate = first
4039
+ ? [first.getDate(), first.getMonth() + 1].map(function (n) { return String(n).padStart(2, '0'); }).join('-') + '-' + first.getFullYear()
4040
+ : '';
4041
+ this.dayLabel = first ? first.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'short', year: 'numeric' }) : '';
4042
+ // ---- teachers
4043
+ var eligible = users.filter(function (u) { return (u.userSubjects || '') !== ''; });
4044
+ var timeByUser = this.groupTime(timed, function (s) { return s.userId; });
4045
+ var sessionsByUser = this.countBy(timed, function (s) { return s.userId; });
4046
+ var activeUserIds = new Set(sessions.map(function (s) { return s.userId; }));
4047
+ var totalTeachingMs = Array.from(timeByUser.values()).reduce(function (a, b) { return a + b; }, 0);
4048
+ var activeEligible = eligible.filter(function (u) { return activeUserIds.has(u.userId); }).length;
4049
+ this.eligibleCount = eligible.length;
4050
+ this.classroomCount = classrooms.length;
4051
+ this.topTeachers = this.toBars(Array.from(timeByUser.entries()).map(function (_a) {
4052
+ var _b = __read(_a, 2), id = _b[0], ms = _b[1];
4053
+ return ({
4054
+ label: _this.userName(userById.get(id), id),
4055
+ sub: (sessionsByUser.get(id) || 0) + " sessions",
4056
+ value: ms,
4057
+ });
4058
+ }), function (ms) { return _this.formatDuration(ms); }, 8);
4059
+ this.inactiveTeachers = eligible
4060
+ .filter(function (u) { return !activeUserIds.has(u.userId); })
4061
+ .map(function (u) { return _this.userName(u, u.userId); })
4062
+ .sort(function (a, b) { return a.localeCompare(b); });
4063
+ // ---- rooms
4064
+ var timeByRoom = this.groupTime(timed, function (s) { return s.ip; });
4065
+ var usedIps = new Set(sessions.map(function (s) { return s.ip; }));
4066
+ var usedClassrooms = classrooms.filter(function (t) { return usedIps.has(t.terminalKey); }).length;
4067
+ var daySpan = timed.length ? Math.max.apply(Math, __spread(timed.map(function (s) { return s.end; }))) - Math.min.apply(Math, __spread(timed.map(function (s) { return s.start; }))) : 0;
4068
+ // every installed classroom plus any other room that was used, in room order
4069
+ var tileIps = classrooms.map(function (t) { return t.terminalKey; }).concat(Array.from(timeByRoom.keys()).filter(function (ip) { return !classrooms.some(function (t) { return t.terminalKey === ip; }); }));
4070
+ var maxRoomMs = Math.max.apply(Math, __spread(Array.from(timeByRoom.values()), [1]));
4071
+ this.roomTiles = tileIps
4072
+ .map(function (ip) {
4073
+ var ms = timeByRoom.get(ip) || 0;
4074
+ var share = daySpan ? Math.round((ms / daySpan) * 100) : 0;
4075
+ return {
4076
+ label: roomByIp.get(ip) || ip,
4077
+ display: ms ? _this.formatDuration(ms) + " \u00B7 " + share + "% of the day" : 'Not used today',
4078
+ share: share,
4079
+ // pale blue for little use through to deep navy for the busiest room
4080
+ shade: share ? _this.navyShade(ms / maxRoomMs) : '',
4081
+ dark: !!share && Math.pow(ms / maxRoomMs, 1.8) > 0.5,
4082
+ // shown as an outlined tile: unused, or used for under 1% of the day
4083
+ idle: !share,
4084
+ // long names ("science lab", "127.0.0.1") take extra columns so they show in full
4085
+ span: Math.min(Math.ceil(((roomByIp.get(ip) || ip).length + 2) / 5), 4),
4086
+ };
4087
+ })
4088
+ .sort(function (a, b) { return a.label.localeCompare(b.label, undefined, { numeric: true }); });
4089
+ this.idleRooms = classrooms
4090
+ .filter(function (t) { return !usedIps.has(t.terminalKey); })
4091
+ .map(function (t) { return roomByIp.get(t.terminalKey); })
4092
+ .sort(function (a, b) { return a.localeCompare(b, undefined, { numeric: true }); });
4093
+ // ---- through the day: distinct teachers in a session during each hour
4094
+ this.hourly = [];
4095
+ if (timed.length) {
4096
+ var startHour = new Date(Math.min.apply(Math, __spread(timed.map(function (s) { return s.start; }))));
4097
+ startHour.setMinutes(0, 0, 0);
4098
+ var lastEnd = Math.max.apply(Math, __spread(timed.map(function (s) { return s.end; })));
4099
+ var counts = [];
4100
+ var _loop_1 = function (t) {
4101
+ var teachers = new Set(timed.filter(function (s) { return s.start < t + HOUR && s.end > t; }).map(function (s) { return s.userId; }));
4102
+ counts.push({ label: this_1.formatHour(new Date(t)), value: teachers.size });
4103
+ };
4104
+ var this_1 = this;
4105
+ for (var t = startHour.getTime(); t < lastEnd; t += HOUR) {
4106
+ _loop_1(t);
4107
+ }
4108
+ var max_1 = Math.max.apply(Math, __spread(counts.map(function (c) { return c.value; }), [1]));
4109
+ this.hourly = counts.map(function (c) { return (Object.assign(Object.assign({}, c), { percent: (c.value / max_1) * 100, peak: c.value === max_1, color: _this.utilisationColor(c.value / max_1) })); });
4110
+ }
4111
+ var peak = this.hourly.reduce(function (best, h) { return (!best || h.value > best.value ? h : best); }, null);
4112
+ // ---- what was taught: session time split evenly across the subjects / grades it touched
4113
+ var subjectTime = new Map();
4114
+ var gradeTime = new Map();
4115
+ timed.forEach(function (s) {
4116
+ var metas = s.resources.map(function (r) { return resourceById.get(r.resourceId); }).filter(function (m) { return !!m; });
4117
+ _this.share(subjectTime, metas.map(function (m) { return _this.title(m.subject); }), s.end - s.start);
4118
+ _this.share(gradeTime, metas.map(function (m) { return _this.title(m.grade); }), s.end - s.start);
4119
+ });
4120
+ this.subjectDonut = this.buildDonut(this.entries(subjectTime), 7);
4121
+ this.grades = this.toBars(this.entries(gradeTime).sort(function (a, b) { return a.label.localeCompare(b.label, undefined, { numeric: true }); }), function (ms) { return _this.formatDuration(ms); }, 0, false);
4122
+ this.gradeColumns = this.grades.map(function (g) { return (Object.assign(Object.assign({}, g), { short: _this.shortGrade(g.label) })); });
4123
+ // ---- content
4124
+ var launches = sessions.reduce(function (acc, s) { return acc.concat(s.resources); }, []);
4125
+ var typeCounts = this.countBy(launches, function (r) { return _this.typeLabel(r.type); });
4126
+ this.contentMix = this.toBars(Array.from(typeCounts.entries()).map(function (_a) {
4127
+ var _b = __read(_a, 2), label = _b[0], n = _b[1];
4128
+ return ({ label: label, value: n });
4129
+ }), function (n) { return "" + n; }, 0).map(function (item, i) { return (Object.assign(Object.assign({}, item), { color: _this.color(i), percent: (item.value / Math.max(launches.length, 1)) * 100 })); });
4130
+ var chapterCounts = new Map();
4131
+ launches.forEach(function (r) {
4132
+ var m = resourceById.get(r.resourceId);
4133
+ var chapter = m && _this.title(m.chapter);
4134
+ if (!chapter)
4135
+ return;
4136
+ var key = _this.title(m.subject) + "|" + _this.title(m.grade) + "|" + chapter;
4137
+ var cur = chapterCounts.get(key) || { n: 0, sub: [_this.title(m.subject), _this.title(m.grade)].filter(function (x) { return x; }).join(' · ') };
4138
+ cur.n++;
4139
+ chapterCounts.set(key, cur);
4140
+ });
4141
+ this.chapters = this.toBars(Array.from(chapterCounts.entries()).map(function (_a) {
4142
+ var _b = __read(_a, 2), key = _b[0], v = _b[1];
4143
+ return ({ label: key.split('|')[2], sub: v.sub, value: v.n });
4144
+ }), function (n) { return n + " uses"; }, 8);
4145
+ // ---- headline numbers
4146
+ var avgSession = timed.length ? totalTeachingMs / timed.length : 0;
4147
+ var topics = new Set(launches.filter(function (r) { return r.type === 'tp'; }).map(function (r) { return r.resourceId; }));
4148
+ this.kpis = [
4149
+ {
4150
+ label: 'Active Teachers',
4151
+ value: activeEligible + " / " + eligible.length,
4152
+ hint: this.pct(activeEligible, eligible.length) + " of eligible teachers",
4153
+ },
4154
+ {
4155
+ label: 'Classrooms Used',
4156
+ value: usedClassrooms + " / " + classrooms.length,
4157
+ hint: this.pct(usedClassrooms, classrooms.length) + " of classrooms",
4158
+ },
4159
+ {
4160
+ label: 'Teaching Time',
4161
+ value: this.formatDuration(totalTeachingMs),
4162
+ hint: this.formatDuration(activeUserIds.size ? totalTeachingMs / activeUserIds.size : 0) + " per active teacher",
4163
+ },
4164
+ {
4165
+ label: 'Sessions',
4166
+ value: "" + timed.length,
4167
+ hint: this.formatDuration(avgSession) + " on average",
4168
+ },
4169
+ {
4170
+ label: 'Content Used',
4171
+ value: "" + launches.length,
4172
+ hint: topics.size + " distinct topics",
4173
+ },
4174
+ {
4175
+ label: 'Peak Hour',
4176
+ value: peak ? peak.label : '—',
4177
+ hint: peak ? peak.value + " teachers in class" : 'No sessions',
4178
+ },
4179
+ ];
4180
+ this.attention = [
4181
+ {
4182
+ label: 'Teachers Not Active',
4183
+ icon: 'teacher',
4184
+ value: this.inactiveTeachers.length,
4185
+ hint: 'Eligible teachers with no session today',
4186
+ },
4187
+ {
4188
+ label: 'Classrooms Not Used',
4189
+ icon: 'room',
4190
+ value: this.idleRooms.length,
4191
+ hint: 'Installed classrooms with no session today',
4192
+ },
4193
+ {
4194
+ label: "Sessions Under " + SHORT_SESSION_MIN + " Min",
4195
+ icon: 'clock',
4196
+ value: timed.filter(function (s) { return s.end - s.start < SHORT_SESSION_MIN * MINUTE; }).length,
4197
+ hint: 'May be accidental log-ins',
4198
+ },
4199
+ {
4200
+ label: 'Sessions Without Content',
4201
+ icon: 'book',
4202
+ value: timed.filter(function (s) { return s.resources.length === 0; }).length,
4203
+ hint: 'Logged in but nothing was used',
4204
+ },
4205
+ ];
4206
+ this.countUp();
4207
+ };
4208
+ // biggest slices first; anything past `keep` is grouped as "Other"
4209
+ InsightsComponent.prototype.buildDonut = function (items, keep) {
4210
+ var _this = this;
4211
+ var sorted = __spread(items).sort(function (a, b) { return b.value - a.value; });
4212
+ var shown = sorted.slice(0, keep);
4213
+ var rest = sorted.slice(keep).reduce(function (sum, i) { return sum + i.value; }, 0);
4214
+ if (rest > 0)
4215
+ shown.push({ label: 'Other', value: rest });
4216
+ var total = shown.reduce(function (sum, i) { return sum + i.value; }, 0);
4217
+ var circumference = 2 * Math.PI * this.donutRadius;
4218
+ var used = 0;
4219
+ var segments = shown.map(function (item, i) {
4220
+ var length = total ? (item.value / total) * circumference : 0;
4221
+ var segment = Object.assign(Object.assign({}, item), { display: _this.formatDuration(item.value), percent: total ? Math.round((item.value / total) * 100) : 0, color: item.label === 'Other' ? '#c9d2e3' : _this.color(i),
4222
+ // small gap between slices
4223
+ dash: Math.max(length - 2, 0) + " " + circumference, offset: -used });
4224
+ used += length;
4225
+ return segment;
4226
+ });
4227
+ return { total: this.formatDuration(total), segments: segments };
4228
+ };
4229
+ // 0 = quiet (light red), 0.5 = moderate (warm yellow), 1 = busiest (green)
4230
+ // `soft` lightens the colour so dark text on tiles stays readable
4231
+ InsightsComponent.prototype.utilisationColor = function (ratio, soft) {
4232
+ if (soft === void 0) { soft = false; }
4233
+ var stops = [
4234
+ { at: 0, h: 4, s: 82, l: 74 },
4235
+ { at: 0.5, h: 43, s: 90, l: 62 },
4236
+ { at: 1, h: 88, s: 45, l: 56 },
4237
+ ];
4238
+ var r = Math.min(Math.max(ratio, 0), 1);
4239
+ var _a = __read(r <= 0.5 ? [stops[0], stops[1]] : [stops[1], stops[2]], 2), a = _a[0], b = _a[1];
4240
+ var t = (r - a.at) / (b.at - a.at);
4241
+ var mix = function (x, y) { return Math.round(x + (y - x) * t); };
4242
+ var light = mix(a.l, b.l) + (soft ? 12 : 0);
4243
+ return "hsl(" + mix(a.h, b.h) + ", " + mix(a.s, b.s) + "%, " + light + "%)";
4244
+ };
4245
+ // 0 = near-white blue, 1 = deep navy
4246
+ InsightsComponent.prototype.navyShade = function (ratio) {
4247
+ var from = [234, 241, 251];
4248
+ var to = [22, 43, 87];
4249
+ // eased so low and mid use stay light and only the busiest rooms turn dark
4250
+ var t = Math.pow(Math.min(Math.max(ratio, 0), 1), 1.8);
4251
+ var c = from.map(function (f, i) { return Math.round(f + (to[i] - f) * t); });
4252
+ return "rgb(" + c[0] + ", " + c[1] + ", " + c[2] + ")";
4253
+ };
4254
+ // "Class 7" -> "7", "Early Childhood Education" -> "ECE"
4255
+ InsightsComponent.prototype.shortGrade = function (label) {
4256
+ var match = /^class\s+(.+)$/i.exec(label);
4257
+ if (match)
4258
+ return match[1];
4259
+ return label.split(/\s+/).filter(function (w) { return w; }).map(function (w) { return w.charAt(0).toUpperCase(); }).join('').slice(0, 4);
4260
+ };
4261
+ // every number inside a value ("42 / 77", "48h 53m") rises from 0 together
4262
+ InsightsComponent.prototype.countUp = function () {
4263
+ var _this = this;
4264
+ cancelAnimationFrame(this.countFrame);
4265
+ var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
4266
+ var apply = function (progress) {
4267
+ _this.kpis.forEach(function (k) { return (k.shown = k.value.replace(/\d+/g, function (n) { return "" + Math.round(+n * progress); })); });
4268
+ _this.attention.forEach(function (a) { return (a.shown = Math.round(a.value * progress)); });
4269
+ };
4270
+ if (reduced) {
4271
+ apply(1);
4272
+ return;
4273
+ }
4274
+ var duration = 900;
4275
+ var start = performance.now();
4276
+ var step = function (now) {
4277
+ var t = Math.min((now - start) / duration, 1);
4278
+ apply(1 - Math.pow(1 - t, 3));
4279
+ if (t < 1)
4280
+ _this.countFrame = requestAnimationFrame(step);
4281
+ };
4282
+ apply(0);
4283
+ this.countFrame = requestAnimationFrame(step);
4284
+ };
4285
+ InsightsComponent.prototype.closeDetails = function () {
4286
+ this.detailsOpen = false;
4287
+ };
4288
+ // "Mrs Alisa Dsouza" -> "AD"
4289
+ InsightsComponent.prototype.initials = function (name) {
4290
+ var words = (name || '').split(/\s+/).filter(function (w) { return w && !/^(mr|mrs|miss|ms|dr)\.?$/i.test(w); });
4291
+ return words.length ? (words[0][0] + (words.length > 1 ? words[words.length - 1][0] : '')).toUpperCase() : '?';
4292
+ };
4293
+ // ---------- PDF export
4294
+ InsightsComponent.prototype.downloadPdf = function () {
4295
+ return __awaiter(this, void 0, void 0, function () {
4296
+ var page, wrapper, copy, top_1, breaks, canvas;
4297
+ return __generator(this, function (_a) {
4298
+ switch (_a.label) {
4299
+ case 0:
4300
+ page = this.host.nativeElement.querySelector('.in-page');
4301
+ if (!page || this.isExporting)
4302
+ return [2 /*return*/];
4303
+ this.isExporting = true;
4304
+ wrapper = document.createElement('div');
4305
+ wrapper.setAttribute('data-pdf-wrapper', '');
4306
+ wrapper.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;overflow:hidden;z-index:-1;';
4307
+ copy = page.cloneNode(true);
4308
+ copy.classList.add('in-exporting');
4309
+ copy.style.width = PDF_RENDER_WIDTH + 'px';
4310
+ wrapper.appendChild(copy);
4311
+ page.parentElement.appendChild(wrapper);
4312
+ _a.label = 1;
4313
+ case 1:
4314
+ _a.trys.push([1, , 3, 4]);
4315
+ top_1 = copy.getBoundingClientRect().top;
4316
+ breaks = Array.from(copy.querySelectorAll('.in-card'))
4317
+ .map(function (card) { return Math.round(card.getBoundingClientRect().top - top_1 - 8); })
4318
+ .filter(function (y, i, all) { return y > 0 && all.indexOf(y) === i; })
4319
+ .sort(function (a, b) { return a - b; });
4320
+ return [4 /*yield*/, html2canvas__default['default'](copy, {
4321
+ scale: 2,
4322
+ backgroundColor: '#ffffff',
4323
+ onclone: function (doc) {
4324
+ var clonedWrapper = doc.querySelector('[data-pdf-wrapper]');
4325
+ if (clonedWrapper) {
4326
+ clonedWrapper.style.overflow = 'visible';
4327
+ }
4328
+ },
4329
+ })];
4330
+ case 2:
4331
+ canvas = _a.sent();
4332
+ this.savePdf(canvas, breaks.map(function (y) { return y * 2; }));
4333
+ return [3 /*break*/, 4];
4334
+ case 3:
4335
+ wrapper.remove();
4336
+ this.isExporting = false;
4337
+ return [7 /*endfinally*/];
4338
+ case 4: return [2 /*return*/];
4339
+ }
4340
+ });
4341
+ });
4342
+ };
4343
+ // A4 portrait; each page takes whole rows of cards and long content is sliced as a last resort
4344
+ InsightsComponent.prototype.savePdf = function (canvas, breaks) {
4345
+ var _this = this;
4346
+ var pdf = new jspdf.jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
4347
+ var margin = 10;
4348
+ var pageWidth = pdf.internal.pageSize.getWidth();
4349
+ var pageHeight = pdf.internal.pageSize.getHeight();
4350
+ var contentWidth = pageWidth - margin * 2;
4351
+ var mmPerPx = contentWidth / canvas.width;
4352
+ var maxSlice = Math.floor((pageHeight - margin * 2 - 6) / mmPerPx);
4353
+ var slices = [];
4354
+ var start = 0;
4355
+ var _loop_2 = function () {
4356
+ var end = Math.min(start + maxSlice, canvas.height);
4357
+ if (end < canvas.height) {
4358
+ var fit = breaks.filter(function (b) { return b > start && b <= end; });
4359
+ if (fit.length)
4360
+ end = fit[fit.length - 1];
4361
+ }
4362
+ slices.push([start, end]);
4363
+ start = end;
4364
+ };
4365
+ while (start < canvas.height) {
4366
+ _loop_2();
4367
+ }
4368
+ slices.forEach(function (_a, i) {
4369
+ var _b = __read(_a, 2), from = _b[0], to = _b[1];
4370
+ var part = document.createElement('canvas');
4371
+ part.width = canvas.width;
4372
+ part.height = to - from;
4373
+ part.getContext('2d').drawImage(canvas, 0, from, canvas.width, to - from, 0, 0, canvas.width, to - from);
4374
+ if (i > 0)
4375
+ pdf.addPage();
4376
+ pdf.addImage(part.toDataURL('image/jpeg', 0.92), 'JPEG', margin, margin, contentWidth, part.height * mmPerPx);
4377
+ pdf.setFontSize(8);
4378
+ pdf.setTextColor(140);
4379
+ pdf.text(_this.schoolName + " \u00B7 Usage insights", margin, pageHeight - 5);
4380
+ pdf.text("Page " + (i + 1) + " of " + slices.length, pageWidth - margin, pageHeight - 5, { align: 'right' });
4381
+ });
4382
+ pdf.save("Insights-" + (this.fileDate || 'report') + ".pdf");
4383
+ };
4384
+ // ---------- helpers
4385
+ InsightsComponent.prototype.list = function (json, outer, inner) {
4386
+ var value = json && json[outer] && json[outer][inner];
4387
+ return Array.isArray(value) ? value : [];
4388
+ };
4389
+ InsightsComponent.prototype.location = function (metadata) {
4390
+ try {
4391
+ return (typeof metadata === 'string' ? JSON.parse(metadata) : metadata || {}).location || '';
4392
+ }
4393
+ catch (e) {
4394
+ return '';
4395
+ }
4396
+ };
4397
+ // "2024-12-10 07:01:24.0" is not valid ISO for every browser, so add the "T"
4398
+ InsightsComponent.prototype.parseTime = function (value) {
4399
+ return value ? new Date(String(value).replace(' ', 'T')).getTime() : NaN;
4400
+ };
4401
+ InsightsComponent.prototype.title = function (item) {
4402
+ return (item && item.title ? String(item.title) : '').trim();
4403
+ };
4404
+ // known types use the dashboard's labels; others like "casset_weblink" become "Weblink"
4405
+ InsightsComponent.prototype.typeLabel = function (type) {
4406
+ if (!type)
4407
+ return 'Other';
4408
+ var label = this.pdService.getDisplaytypeValue(type);
4409
+ if (label !== type)
4410
+ return label;
4411
+ var words = type.replace(/^c?asset_/, '').replace(/_/g, ' ');
4412
+ return words.charAt(0).toUpperCase() + words.slice(1);
4413
+ };
4414
+ InsightsComponent.prototype.userName = function (user, fallback) {
4415
+ return user ? this.pdService.getName(user) : fallback;
4416
+ };
4417
+ // overlapping sessions for the same key are counted once
4418
+ InsightsComponent.prototype.groupTime = function (sessions, key) {
4419
+ var _this = this;
4420
+ var grouped = new Map();
4421
+ sessions.forEach(function (s) {
4422
+ var k = key(s);
4423
+ grouped.set(k, (grouped.get(k) || []).concat({ start: s.start, end: s.end }));
4424
+ });
4425
+ var result = new Map();
4426
+ grouped.forEach(function (intervals, k) { return result.set(k, _this.mergedDuration(intervals)); });
4427
+ return result;
4428
+ };
4429
+ InsightsComponent.prototype.mergedDuration = function (intervals) {
4430
+ var sorted = __spread(intervals).sort(function (a, b) { return a.start - b.start; });
4431
+ var total = 0;
4432
+ var cur = null;
4433
+ sorted.forEach(function (i) {
4434
+ if (!cur || i.start > cur.end) {
4435
+ if (cur)
4436
+ total += cur.end - cur.start;
4437
+ cur = Object.assign({}, i);
4438
+ }
4439
+ else if (i.end > cur.end) {
4440
+ cur.end = i.end;
4441
+ }
4442
+ });
4443
+ return cur ? total + cur.end - cur.start : total;
4444
+ };
4445
+ InsightsComponent.prototype.countBy = function (items, key) {
4446
+ var result = new Map();
4447
+ items.forEach(function (item) { return result.set(key(item), (result.get(key(item)) || 0) + 1); });
4448
+ return result;
4449
+ };
4450
+ InsightsComponent.prototype.share = function (target, labels, ms) {
4451
+ var unique = Array.from(new Set(labels.filter(function (l) { return l; })));
4452
+ unique.forEach(function (l) { return target.set(l, (target.get(l) || 0) + ms / unique.length); });
4453
+ };
4454
+ InsightsComponent.prototype.entries = function (map) {
4455
+ return Array.from(map.entries()).map(function (_a) {
4456
+ var _b = __read(_a, 2), label = _b[0], value = _b[1];
4457
+ return ({ label: label, value: value });
4458
+ });
4459
+ };
4460
+ // limit 0 = show all; bar length is relative to the largest value
4461
+ InsightsComponent.prototype.toBars = function (items, display, limit, sortByValue) {
4462
+ if (sortByValue === void 0) { sortByValue = true; }
4463
+ var sorted = sortByValue ? __spread(items).sort(function (a, b) { return b.value - a.value; }) : items;
4464
+ var shown = limit ? sorted.slice(0, limit) : sorted;
4465
+ var max = Math.max.apply(Math, __spread(shown.map(function (i) { return i.value; }), [1]));
4466
+ return shown.map(function (i) { return (Object.assign(Object.assign({}, i), { display: display(i.value), percent: (i.value / max) * 100 })); });
4467
+ };
4468
+ // blue and yellow first to match the page, then supporting tones
4469
+ InsightsComponent.prototype.color = function (index) {
4470
+ var palette = ['#7c9cf5', '#f6d27e', '#a9bffa', '#6fc6ba', '#f4a98a', '#c0aef7', '#f3a6c8', '#b4bfcf', '#fbe7b4'];
4471
+ return palette[index % palette.length];
4472
+ };
4473
+ InsightsComponent.prototype.pct = function (part, whole) {
4474
+ return whole ? Math.round((part / whole) * 100) + "%" : '0%';
4475
+ };
4476
+ InsightsComponent.prototype.formatHour = function (date) {
4477
+ var h = date.getHours();
4478
+ return (h % 12 || 12) + " " + (h >= 12 ? 'PM' : 'AM');
4479
+ };
4480
+ InsightsComponent.prototype.formatDuration = function (ms) {
4481
+ var minutes = Math.round(ms / MINUTE);
4482
+ var h = Math.floor(minutes / 60);
4483
+ var m = minutes % 60;
4484
+ return h ? h + "h " + m + "m" : m + "m";
4485
+ };
4486
+ return InsightsComponent;
4487
+ }());
4488
+ InsightsComponent.decorators = [
4489
+ { type: i0.Component, args: [{
4490
+ selector: 'lib-insights',
4491
+ template: "<div class=\"in-page\">\n <!-- title and headline numbers stay in view while the cards below scroll -->\n <div class=\"in-sticky\">\n <div class=\"in-heading\">\n <div class=\"in-heading-text\">{{ schoolName }}<span *ngIf=\"schoolName && dayLabel\"> \u00B7 </span>{{ dayLabel }}</div>\n <button\n type=\"button\"\n class=\"in-pdf-btn\"\n *ngIf=\"hasSessions\"\n data-html2canvas-ignore=\"true\"\n [disabled]=\"isExporting\"\n (click)=\"downloadPdf()\"\n >{{ isExporting ? 'Preparing PDF\u2026' : 'Download PDF' }}</button>\n </div>\n\n <div class=\"in-kpis\" *ngIf=\"hasSessions\">\n <div class=\"in-kpi in-anim\" *ngFor=\"let kpi of kpis; let i = index; let last = last\" [class.in-kpi-accent]=\"last\" [style.animation-delay.ms]=\"i * 60\">\n <span class=\"in-kpi-icon\">\n <svg viewBox=\"0 0 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"><path d=\"M2.5 7.5l5-5M3.5 2.5h4v4\" /></svg>\n </span>\n <div class=\"in-kpi-label\">{{ kpi.label }}</div>\n <div class=\"in-kpi-value\">{{ kpi.shown || kpi.value }}</div>\n <div class=\"in-kpi-hint\">{{ kpi.hint }}</div>\n </div>\n </div>\n </div>\n\n <div class=\"in-empty\" *ngIf=\"!hasSessions\">No usage was recorded on this day.</div>\n\n <ng-container *ngIf=\"hasSessions\">\n\n <!-- primary: what to act on, and who is using the system -->\n <div class=\"in-grid\">\n <section [style.animation-delay.ms]=\"120\" class=\"in-anim in-card in-wide in-primary\">\n <div class=\"in-card-title\">Needs Attention</div>\n <div class=\"in-card-sub\">Who and what to follow up on today</div>\n <div class=\"in-attention\">\n <div class=\"in-attention-item in-anim-row\" *ngFor=\"let a of attention; let i = index\" [style.animation-delay.ms]=\"300 + i * 80\">\n <div class=\"in-attention-value\" [class.in-zero]=\"a.value === 0\">{{ a.shown != null ? a.shown : a.value }}</div>\n <div>\n <div class=\"in-attention-label\">\n <span class=\"in-inline-icon\" [ngSwitch]=\"a.icon\" aria-hidden=\"true\">\n <svg *ngSwitchCase=\"'teacher'\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"8\" r=\"4\" /><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\" /></svg>\n <svg *ngSwitchCase=\"'room'\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" /><path d=\"M12 16v4M8 20h8M7 9h6\" /></svg>\n <svg *ngSwitchCase=\"'clock'\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\" /><path d=\"M12 7v5l3 2\" /></svg>\n <svg *ngSwitchCase=\"'book'\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 5a2 2 0 0 1 2-2h13v16H6a2 2 0 0 0-2 2V5z\" /><path d=\"M4 19a2 2 0 0 1 2-2h13\" /></svg>\n </span>\n {{ a.label }}\n </div>\n <div class=\"in-muted\">{{ a.hint }}</div>\n </div>\n </div>\n </div>\n <!-- who to follow up: big tiles that shrink to slim bars when the lists open below (accordion) -->\n <div class=\"in-follow\" [class.in-follow-compact]=\"detailsOpen\">\n <button type=\"button\" class=\"in-follow-tile in-follow-teachers\" [attr.aria-expanded]=\"detailsOpen\" aria-controls=\"in-follow-details\" (click)=\"detailsOpen = !detailsOpen\">\n <span class=\"in-follow-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"8\" r=\"4\" /><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\" /></svg>\n </span>\n <span class=\"in-follow-number\">{{ attention[0]?.shown != null ? attention[0].shown : inactiveTeachers.length }}</span>\n <span class=\"in-follow-label-big\">Teachers Not Active</span>\n <span class=\"in-muted\">of {{ eligibleCount }} eligible teachers</span>\n <span class=\"in-avatars\" *ngIf=\"inactiveTeachers.length\">\n <span class=\"in-avatar\" *ngFor=\"let name of inactiveTeachers.slice(0, 7)\" [title]=\"name\">{{ initials(name) }}</span>\n <span class=\"in-avatar in-avatar-more\" *ngIf=\"inactiveTeachers.length > 7\">+{{ inactiveTeachers.length - 7 }}</span>\n </span>\n </button>\n <button type=\"button\" class=\"in-follow-tile in-follow-rooms\" [attr.aria-expanded]=\"detailsOpen\" aria-controls=\"in-follow-details\" (click)=\"detailsOpen = !detailsOpen\">\n <span class=\"in-follow-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" /><path d=\"M12 16v4M8 20h8M7 9h6\" /></svg>\n </span>\n <span class=\"in-follow-number\">{{ attention[1]?.shown != null ? attention[1].shown : idleRooms.length }}</span>\n <span class=\"in-follow-label-big\">Classrooms Not Used</span>\n <span class=\"in-muted\">of {{ classroomCount }} installed classrooms</span>\n <span class=\"in-room-chips\" *ngIf=\"idleRooms.length\">\n <span class=\"in-room-chip\" *ngFor=\"let room of idleRooms.slice(0, 6)\">{{ room }}</span>\n <span class=\"in-room-chip in-avatar-more\" *ngIf=\"idleRooms.length > 6\">+{{ idleRooms.length - 6 }}</span>\n </span>\n </button>\n </div>\n <div class=\"in-follow-details\" id=\"in-follow-details\" *ngIf=\"detailsOpen\">\n <ng-container *ngTemplateOutlet=\"followLists\"></ng-container>\n </div>\n <div class=\"in-follow-foot\" data-html2canvas-ignore=\"true\">\n <button type=\"button\" class=\"in-link-btn\" [attr.aria-expanded]=\"detailsOpen\" aria-controls=\"in-follow-details\" (click)=\"detailsOpen = !detailsOpen\">\n {{ detailsOpen ? 'Hide details' : 'Show details' }}\n <span class=\"in-link-chevron\" [class.in-link-chevron-up]=\"detailsOpen\" aria-hidden=\"true\"></span>\n </button>\n </div>\n <!-- the PDF can't open a modal, so it prints the full lists here instead -->\n <div class=\"in-print-only\">\n <ng-container *ngTemplateOutlet=\"followLists\"></ng-container>\n </div>\n </section>\n <section [style.animation-delay.ms]=\"180\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Most Active Teachers</div>\n <div class=\"in-card-sub\">Time in class, overlapping sessions counted once</div>\n <ng-container *ngTemplateOutlet=\"bars; context: { $implicit: topTeachers, color: '#8BC34A' }\"></ng-container>\n </section>\n </div>\n\n <!-- supporting detail -->\n <div class=\"in-grid\">\n <section [style.animation-delay.ms]=\"240\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Activity Through the Day</div>\n <div class=\"in-card-sub\">Number of teachers in a session during each hour</div>\n <div class=\"in-hours\">\n <div class=\"in-hour\" *ngFor=\"let h of hourly; let i = index\" [title]=\"h.label + ': ' + h.value + ' teachers'\">\n <div class=\"in-hour-value\">\n <svg class=\"in-hour-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"8\" r=\"4\" /><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\" /></svg>\n {{ h.value }}\n </div>\n <div class=\"in-hour-track\">\n <div class=\"in-hour-bar\" [class.in-peak]=\"h.peak\" [style.height.%]=\"h.percent\" [style.background-color]=\"h.color\" [style.animation-delay.ms]=\"250 + i * 50\"></div>\n </div>\n <div class=\"in-hour-label\">{{ h.label }}</div>\n </div>\n </div>\n <div class=\"in-util-legend\">\n <span class=\"in-muted\">Low</span>\n <span class=\"in-util-scale\"></span>\n <span class=\"in-muted\">High</span>\n </div>\n </section>\n <section [style.animation-delay.ms]=\"300\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Most Used Chapters</div>\n <div class=\"in-card-sub\">Chapters and their topics used today</div>\n <ng-container *ngTemplateOutlet=\"bars; context: { $implicit: chapters, color: '#f4a98a' }\"></ng-container>\n </section>\n <section [style.animation-delay.ms]=\"360\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Content Utilisation</div>\n <div class=\"in-card-sub\">What teachers used in class</div>\n <div class=\"in-stack\">\n <div\n class=\"in-stack-part\"\n *ngFor=\"let c of contentMix; let i = index\"\n [style.animation-delay.ms]=\"300 + i * 60\"\n [style.width.%]=\"c.percent\"\n [style.background-color]=\"c.color\"\n [title]=\"c.label + ': ' + c.display\"\n ></div>\n </div>\n <div class=\"in-legend\">\n <div class=\"in-legend-item\" *ngFor=\"let c of contentMix\">\n <span class=\"in-dot\" [style.background-color]=\"c.color\"></span>\n <span class=\"in-legend-label\">{{ c.label }}</span>\n <span class=\"in-legend-value\">{{ c.display }}</span>\n </div>\n </div>\n </section>\n </div>\n\n <!-- what was taught, and where -->\n <div class=\"in-grid\">\n <section [style.animation-delay.ms]=\"420\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Time by Subject</div>\n <div class=\"in-card-sub\">Share of session time by subject</div>\n <div class=\"in-donut-wrap\">\n <svg class=\"in-donut\" viewBox=\"0 0 140 140\" role=\"img\" aria-label=\"Time by subject\">\n <circle cx=\"70\" cy=\"70\" [attr.r]=\"donutRadius\" class=\"in-donut-track\"></circle>\n <circle\n *ngFor=\"let s of subjectDonut.segments; let i = index\"\n class=\"in-donut-seg\"\n cx=\"70\"\n cy=\"70\"\n [attr.r]=\"donutRadius\"\n [attr.stroke]=\"s.color\"\n [attr.stroke-dasharray]=\"s.dash\"\n [attr.stroke-dashoffset]=\"s.offset\"\n [style.animation-delay.ms]=\"300 + i * 90\"\n >\n <title>{{ s.label }}: {{ s.display }} ({{ s.percent }}%)</title>\n </circle>\n </svg>\n <div class=\"in-donut-center\">\n <div class=\"in-donut-total\">{{ subjectDonut.total }}</div>\n <div class=\"in-muted\" title=\"Session time where content was used\">with content</div>\n </div>\n </div>\n <div class=\"in-legend in-legend-compact\">\n <div class=\"in-legend-item\" *ngFor=\"let s of subjectDonut.segments\">\n <span class=\"in-dot\" [style.background-color]=\"s.color\"></span>\n <span class=\"in-legend-label\" [title]=\"s.label\">{{ s.label }}</span>\n <span class=\"in-legend-value\">{{ s.percent }}%</span>\n </div>\n </div>\n </section>\n <section [style.animation-delay.ms]=\"480\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Classroom Usage</div>\n <div class=\"in-card-sub\">Each classroom's utilisation by time</div>\n <div class=\"in-tiles\">\n <div\n class=\"in-tile\"\n *ngFor=\"let r of roomTiles; let i = index\"\n [class.in-tile-idle]=\"r.idle\"\n [class.in-tile-dark]=\"r.dark\"\n [style.grid-column]=\"'span ' + r.span\"\n [style.background-color]=\"r.shade\"\n [style.animation-delay.ms]=\"300 + i * 15\"\n [title]=\"r.label + ': ' + r.display\"\n >\n <span class=\"in-tile-name\">\n <svg class=\"in-tile-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" /><path d=\"M12 16v4M8 20h8\" /></svg>{{ r.label }}\n </span>\n <span class=\"in-tile-value\">{{ r.share }}%</span>\n </div>\n </div>\n <div class=\"in-tiles-legend\">\n <span class=\"in-tile in-tile-key in-tile-idle\"></span><span class=\"in-muted\">0% / not used</span>\n <span class=\"in-scale\"></span><span class=\"in-muted\">Low \u2192 high utilisation</span>\n </div>\n </section>\n <section [style.animation-delay.ms]=\"540\" class=\"in-anim in-card\">\n <div class=\"in-card-title\">Time by Class</div>\n <div class=\"in-card-sub\">Session time per class</div>\n <div class=\"in-columns\">\n <div class=\"in-column\" *ngFor=\"let g of gradeColumns; let i = index\" [title]=\"g.label + ': ' + g.display\">\n <div class=\"in-column-track\">\n <div class=\"in-column-bar\" [style.height.%]=\"g.percent\" [style.animation-delay.ms]=\"300 + i * 50\"></div>\n </div>\n <div class=\"in-column-label\">{{ g.short }}</div>\n </div>\n </div>\n <div class=\"in-columns-caption\">Class</div>\n <div class=\"in-column-top\" *ngIf=\"gradeColumns.length\">\n <span class=\"in-muted\">Most time</span>\n <ng-container *ngFor=\"let g of gradeColumns\">\n <strong *ngIf=\"g.percent === 100\">{{ g.label }} \u00B7 {{ g.display }}</strong>\n </ng-container>\n </div>\n </section>\n </div>\n </ng-container>\n\n</div>\n\n<ng-template #followLists>\n <div class=\"in-follow-cols\">\n <div class=\"in-follow-col\">\n <div class=\"in-follow-label\">Teachers Not Active <span class=\"in-count\">{{ inactiveTeachers.length }}</span></div>\n <ul class=\"in-name-list\" *ngIf=\"inactiveTeachers.length\">\n <li *ngFor=\"let name of inactiveTeachers\">{{ name }}</li>\n </ul>\n <span class=\"in-muted\" *ngIf=\"inactiveTeachers.length === 0\">Every eligible teacher used the system today.</span>\n </div>\n <div class=\"in-follow-col\">\n <div class=\"in-follow-label\">Classrooms Not Used <span class=\"in-count\">{{ idleRooms.length }}</span></div>\n <ul class=\"in-name-list in-room-list\" *ngIf=\"idleRooms.length\">\n <li *ngFor=\"let room of idleRooms\">{{ room }}</li>\n </ul>\n <span class=\"in-muted\" *ngIf=\"idleRooms.length === 0\">Every classroom was used today.</span>\n </div>\n </div>\n</ng-template>\n\n<!-- each section passes its own bar colour so neighbouring cards are easy to tell apart -->\n<ng-template #bars let-items let-color=\"color\">\n <div class=\"in-bars\">\n <div class=\"in-muted\" *ngIf=\"items.length === 0\">No data</div>\n <div class=\"in-bar-row\" *ngFor=\"let item of items; let i = index\">\n <div class=\"in-bar-head\">\n <span class=\"in-bar-label\" [title]=\"item.label\">{{ item.label }}</span>\n <span class=\"in-bar-value\">{{ item.display }}</span>\n </div>\n <div class=\"in-bar-sub\" *ngIf=\"item.sub\">{{ item.sub }}</div>\n <div class=\"in-bar-track\"><div class=\"in-bar-fill\" [style.width.%]=\"item.percent\" [style.background-color]=\"color\" [style.animation-delay.ms]=\"300 + i * 60\"></div></div>\n </div>\n </div>\n</ng-template>\n",
4492
+ styles: ["@import url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap\");.in-page{font-family:Poppins,Segoe UI,Arial,sans-serif;font-size:13px;color:#1e2437;padding:0 20px 24px;box-sizing:border-box;text-align:left;overflow-y:auto;background:#eef2f9}.in-sticky{position:sticky;top:0;z-index:2;margin:0 -20px 16px;padding:16px 20px;background:#eef2f9;box-shadow:0 6px 12px -10px rgba(30,36,55,.25)}.in-heading{position:relative;margin-bottom:14px;min-height:34px;display:flex;align-items:center;justify-content:center}.in-heading-text{font-size:17px;font-weight:600;color:#1e2437;text-align:center;padding:0 150px}.in-pdf-btn{position:absolute;right:0;top:50%;transform:translateY(-50%);height:34px;padding:0 16px;border:none;border-radius:10px;background:#5b84ec;color:#fff;font:500 13px Poppins,Segoe UI,Arial,sans-serif;cursor:pointer;box-shadow:0 4px 10px rgba(91,132,236,.22)}.in-pdf-btn:hover:not([disabled]){background:#406fe9}.in-pdf-btn[disabled]{cursor:progress;background:#b3c6f5;box-shadow:none}.in-page.in-exporting{height:auto!important;overflow:visible}.in-page.in-exporting *,.in-page.in-exporting :after,.in-page.in-exporting :before{-webkit-animation:none!important;animation:none!important}.in-page.in-exporting .in-sticky{position:static;box-shadow:none}.in-page.in-exporting .in-chips{max-height:none;overflow:visible}.in-page.in-exporting .in-print-only{display:block}.in-page.in-exporting .in-follow-details{display:none}.in-card-sub,.in-muted{font-size:12px;color:#8a93a6}.in-empty{padding:40px;text-align:center;color:#8a93a6}.in-kpis{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:12px}.in-kpi{position:relative;min-width:0;padding:12px 14px;border-radius:14px;background:#fff;box-shadow:0 1px 3px rgba(30,36,55,.06);color:#1e2437;box-sizing:border-box}.in-kpi.in-kpi-accent{background:#5b84ec;color:#fff}.in-kpi.in-kpi-accent .in-kpi-hint,.in-kpi.in-kpi-accent .in-kpi-label{color:hsla(0,0%,100%,.85)}.in-kpi.in-kpi-accent .in-kpi-icon{border-color:#fff;color:#fff}.in-kpi-icon{position:absolute;top:12px;right:12px;width:20px;height:20px;border:2px solid #1e2437;border-radius:50%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;color:#1e2437}.in-kpi-icon svg{width:10px;height:10px}.in-kpi-label{padding-right:24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:12px;font-weight:500;color:rgba(30,36,55,.75)}.in-kpi-value{white-space:nowrap;font-size:22px;font-weight:700;margin:2px 0;font-variant-numeric:tabular-nums}.in-kpi-hint{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:11px;color:rgba(30,36,55,.7)}.in-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.in-grid+.in-grid{margin-top:16px}.in-card{background:#fff;border-radius:16px;box-shadow:0 1px 3px rgba(30,36,55,.06);padding:18px 20px;min-width:0;box-sizing:border-box}.in-card,.in-wide{display:flex;flex-direction:column}.in-wide{grid-column:span 2}.in-card-title{font-size:16px;font-weight:600;color:#1e2437}.in-card-sub{margin:2px 0 16px}.in-count{display:inline-block;min-width:22px;padding:0 8px;margin-left:6px;border-radius:11px;background:#fff6de;color:#a46b00;font-size:12px;font-weight:600;text-align:center;line-height:22px;vertical-align:middle}.in-hours{flex:1 1 auto;display:flex;align-items:stretch;gap:4px;min-height:200px;max-height:320px}.in-hour{flex:1 1 0;flex-direction:column;min-width:0}.in-hour,.in-hour-value{display:flex;align-items:center}.in-hour-value{gap:3px;font-size:12px;font-weight:600;color:#1e2437;margin-bottom:6px;white-space:nowrap}.in-hour-icon{width:12px;height:12px;color:#8a93a6;flex:0 0 auto}.in-hour-track{flex:1 1 auto;max-width:44px;display:flex;align-items:flex-end;background:#f1f4f9;overflow:hidden}.in-hour-bar,.in-hour-track{width:100%;border-radius:999px}.in-hour-bar{min-height:6px;background:#fbe0a0}.in-util-legend{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:12px}.in-util-scale{width:90px;height:8px;border-radius:999px;background:linear-gradient(90deg,#f38e86,#f5c447,#92c15c)}.in-hour-label{margin-top:8px;font-size:10px;color:#8a93a6;white-space:nowrap}.in-bars{display:flex;flex-direction:column;gap:12px}.in-bar-head{display:flex;justify-content:space-between;gap:12px}.in-bar-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.in-bar-value{flex:0 0 auto;color:#1e2437;font-weight:600;font-variant-numeric:tabular-nums}.in-bar-sub{font-size:11px;color:#8a93a6}.in-bar-track{height:8px;margin-top:6px;background:#f1f4f9;border-radius:999px;overflow:hidden}.in-bar-fill{height:100%;border-radius:999px;background:#5b84ec}.in-donut-wrap{position:relative;width:150px;height:150px;margin:0 auto 14px}.in-donut{width:100%;height:100%;transform:rotate(-90deg)}.in-donut circle{fill:none;stroke-width:18}.in-donut-track{stroke:#f1f4f9}.in-donut-seg{stroke-linecap:butt;transition:stroke-width .15s;-webkit-animation:in-draw 1s cubic-bezier(.22,1,.36,1) both;animation:in-draw 1s cubic-bezier(.22,1,.36,1) both}.in-donut-seg:hover{stroke-width:22}@-webkit-keyframes in-draw{0%{stroke-dasharray:0 400}}@keyframes in-draw{0%{stroke-dasharray:0 400}}.in-donut-center{position:absolute;inset:0;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none}.in-donut-total{font-size:18px;font-weight:700}.in-legend-compact{gap:6px}.in-legend-compact .in-legend-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.in-columns{flex:1 1 auto;display:flex;align-items:stretch;gap:5px;min-height:180px;max-height:340px}.in-column{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center}.in-column-track{flex:1 1 auto;width:100%;max-width:22px;display:flex;align-items:flex-end;background:#f1f4f9;border-radius:999px;overflow:hidden}.in-column-bar{width:100%;min-height:4px;border-radius:999px;background:#f3cb6c;-webkit-animation:in-grow-height .8s cubic-bezier(.22,1,.36,1) both;animation:in-grow-height .8s cubic-bezier(.22,1,.36,1) both}.in-column:hover .in-column-bar{background:#f0be46}.in-column-label{margin-top:6px;font-size:11px;font-weight:500;color:#8a93a6;white-space:nowrap}.in-columns-caption{text-align:center;font-size:11px;color:#8a93a6;margin-top:2px}.in-column-top{margin-top:14px;padding:10px 12px;border-radius:10px;background:#fff6de;display:flex;justify-content:space-between;gap:8px;font-size:12px}.in-tiles{display:grid;grid-auto-flow:row dense;grid-template-columns:repeat(auto-fill,minmax(58px,1fr));gap:8px}.in-tile{height:58px;border-radius:12px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;color:#162b57;cursor:default;-webkit-animation:in-pop .4s ease-out both;animation:in-pop .4s ease-out both;transition:transform .12s}.in-tile:hover{transform:scale(1.06)}.in-tile.in-tile-dark{color:#fff}.in-tile.in-tile-idle{background:#f4f7fd;border:2px solid #162b57;color:#162b57}@-webkit-keyframes in-pop{0%{opacity:0;transform:scale(.7)}}@keyframes in-pop{0%{opacity:0;transform:scale(.7)}}.in-tile-icon{width:13px;height:13px;margin-right:4px;vertical-align:-1px}.in-tile-name{max-width:100%;padding:0 4px;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:700}.in-tile-value{font-size:12px;font-weight:500;opacity:.85}.in-tiles-legend{display:flex;align-items:center;gap:6px;margin-top:14px;flex-wrap:wrap}.in-tiles-legend .in-tile-key{width:14px;height:14px;border-radius:4px;-webkit-animation:none;animation:none}.in-scale{width:46px;height:10px;margin-left:8px;border-radius:999px;background:linear-gradient(90deg,#eaf1fb,#162b57)}.in-primary .in-attention{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.in-primary .in-attention-item{gap:14px;padding:12px 14px}.in-primary .in-attention-value{flex:0 0 42px;height:42px;line-height:42px;border-radius:10px;background:#ef6a3a;color:#fff;font-size:16px}.in-primary .in-attention-value.in-zero{background:#8bc34a;color:#fff}.in-primary .in-attention-label{display:flex;align-items:center;gap:6px;font-size:14px}.in-primary .in-attention-item .in-muted{font-size:12px;margin-top:2px}.in-inline-icon{display:inline-flex;color:#ef6a3a}.in-inline-icon svg{width:16px;height:16px}.in-follow{flex:1 1 auto;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-top:18px}.in-follow-tile{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;min-width:0;padding:18px 20px;border:none;border-radius:32px;font:inherit;color:inherit;text-align:center;cursor:pointer;transition:transform .15s,box-shadow .15s}.in-follow-tile:hover{transform:translateY(-2px);box-shadow:0 6px 16px rgba(30,36,55,.08)}.in-follow-teachers{background:#eaf0fd}.in-follow-teachers .in-follow-icon{color:#5b84ec}.in-follow-rooms{background:#eaf0fd}.in-follow-rooms .in-follow-icon{color:#5b84ec}.in-follow-icon{margin-bottom:6px;display:flex;align-items:center;justify-content:center}.in-follow-icon svg{width:40px;height:40px}.in-follow-number{font-size:34px;font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}.in-follow-tile>.in-muted{font-size:15px;color:#1e2437}.in-follow-label-big{font-size:18px;font-weight:600}.in-avatars{flex-wrap:wrap;margin-top:12px;padding-left:8px}.in-avatar,.in-avatars{display:flex;justify-content:center}.in-avatar{width:34px;height:34px;margin-left:-6px;border-radius:50%;border:1.5px solid #5b84ec;background:#fff;color:#3f5fae;font-size:11px;font-weight:600;align-items:center;box-sizing:border-box}.in-avatar-more{background:#5b84ec;color:#fff}.in-room-chips{flex-wrap:wrap;gap:6px;margin-top:12px}.in-room-chip,.in-room-chips{display:flex;justify-content:center}.in-room-chip{min-width:34px;height:34px;padding:0 10px;border-radius:999px;border:1.5px solid #5b84ec;background:#fff;color:#5b84ec;font-size:12px;font-weight:600;align-items:center;box-sizing:border-box}.in-room-chip.in-avatar-more{background:#5b84ec;color:#fff}.in-follow-foot{display:flex;justify-content:flex-end;margin-top:10px}.in-link-btn{border:none;background:transparent;padding:6px 4px;font:600 13px Poppins,Segoe UI,Arial,sans-serif;color:#5b84ec;cursor:pointer}.in-link-btn:hover{text-decoration:underline}.in-print-only{display:none;margin-top:16px}.in-follow-compact{flex:none}.in-follow-compact .in-follow-tile{flex-direction:row;justify-content:flex-start;gap:12px;padding:10px 16px;text-align:left}.in-follow-compact .in-follow-icon{margin:0}.in-follow-compact .in-follow-icon svg{width:24px;height:24px}.in-follow-compact .in-follow-number{font-size:22px}.in-follow-compact .in-avatars,.in-follow-compact .in-follow-tile>.in-muted,.in-follow-compact .in-room-chips{display:none}.in-follow-details{flex:none;margin-top:12px;padding:14px 16px;border:1px solid #eef1f6;border-radius:12px;-webkit-animation:in-rise .3s ease-out both;animation:in-rise .3s ease-out both}.in-link-chevron{display:inline-block;width:6px;height:6px;margin-left:6px;border-right:2px solid;border-bottom:2px solid;transform:rotate(45deg) translateY(-2px);transition:transform .2s}.in-link-chevron.in-link-chevron-up{transform:rotate(-135deg) translateY(-1px)}.in-follow-cols{display:grid;grid-template-columns:3fr 1fr;gap:24px}.in-follow-col{min-width:0}.in-follow-col+.in-follow-col{border-left:1px solid #e3e8f0;padding-left:24px}.in-follow-label{font-size:13px;font-weight:600;margin:2px 0 10px}.in-name-list{margin:0;padding:0;list-style:none;-moz-column-width:160px;column-width:160px;-moz-column-gap:20px;column-gap:20px}.in-name-list li{position:relative;padding:3px 0 3px 14px;-moz-column-break-inside:avoid;break-inside:avoid;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.in-name-list li:before{content:\"\";position:absolute;left:0;top:50%;width:6px;height:6px;margin-top:-3px;border-radius:50%;background:#a9bffa}.in-room-list li:before{background:#fbe0a0}.in-attention{display:flex;flex-direction:column;gap:10px}.in-attention-item{display:flex;align-items:center;gap:12px;padding:8px;border-radius:12px;background:#f7f9fc}.in-attention-value{flex:0 0 44px;height:40px;line-height:40px;text-align:center;border-radius:10px;background:#fbe0a0;color:#1e2437;font-size:16px;font-weight:700}.in-attention-value.in-zero{background:#eaf0fd;color:#5b84ec}.in-attention-label{font-weight:600}.in-stack{display:flex;height:14px;border-radius:999px;overflow:hidden;background:#f1f4f9;margin-bottom:16px}.in-stack-part{height:100%;box-shadow:inset -2px 0 0 #fff}.in-legend{display:flex;flex-direction:column;gap:8px}.in-legend-item{display:flex;align-items:center;gap:10px}.in-dot{flex:0 0 auto;width:10px;height:10px;border-radius:3px}.in-legend-label{flex:1 1 auto}.in-legend-value{font-weight:600;color:#1e2437;font-variant-numeric:tabular-nums}.in-chips{display:flex;flex-wrap:wrap;gap:8px;max-height:230px;overflow:auto}.in-chip{padding:5px 12px;border-radius:999px;background:#eaf0fd;color:#3f5fae;font-size:12px;font-weight:500}@-webkit-keyframes in-rise{0%{opacity:0;transform:translateY(14px)}}@keyframes in-rise{0%{opacity:0;transform:translateY(14px)}}@-webkit-keyframes in-fade-right{0%{opacity:0;transform:translateX(-10px)}}@keyframes in-fade-right{0%{opacity:0;transform:translateX(-10px)}}@-webkit-keyframes in-grow-width{0%{width:0}}@keyframes in-grow-width{0%{width:0}}@-webkit-keyframes in-grow-height{0%{height:0}}@keyframes in-grow-height{0%{height:0}}.in-anim{-webkit-animation:in-rise .5s cubic-bezier(.22,1,.36,1) both;animation:in-rise .5s cubic-bezier(.22,1,.36,1) both}.in-anim-row{-webkit-animation:in-fade-right .45s ease-out both;animation:in-fade-right .45s ease-out both}.in-bar-fill,.in-stack-part{-webkit-animation:in-grow-width .9s cubic-bezier(.22,1,.36,1) both;animation:in-grow-width .9s cubic-bezier(.22,1,.36,1) both}.in-hour-bar{-webkit-animation:in-grow-height .8s cubic-bezier(.22,1,.36,1) both;animation:in-grow-height .8s cubic-bezier(.22,1,.36,1) both}@media (prefers-reduced-motion:reduce){.in-anim,.in-anim-row,.in-bar-fill,.in-column-bar,.in-donut-seg,.in-hour-bar,.in-stack-part,.in-tile{-webkit-animation:none;animation:none}}@media (max-width:900px){.in-kpis{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (max-width:1100px){.in-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.in-grid>.in-card:last-child:nth-child(odd),.in-wide+.in-card{grid-column:span 2}}@media (max-width:760px){.in-heading-text{padding:0;text-align:left;flex:1 1 auto}.in-pdf-btn{position:static;transform:none}.in-grid>.in-card:last-child:nth-child(odd),.in-wide,.in-wide+.in-card{grid-column:auto}.in-follow,.in-follow-cols,.in-primary .in-attention{grid-template-columns:1fr}.in-follow-col+.in-follow-col{border-left:none;padding-left:0;border-top:1px solid #e3e8f0;padding-top:16px}.in-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.in-grid{grid-template-columns:1fr}}"]
4493
+ },] }
4494
+ ];
4495
+ InsightsComponent.ctorParameters = function () { return [
4496
+ { type: PrincipalDashboardLibV2Service },
4497
+ { type: i0.ElementRef }
4498
+ ]; };
4499
+ InsightsComponent.propDecorators = {
4500
+ data: [{ type: i0.Input }],
4501
+ fitHeight: [{ type: i0.HostListener, args: ['window:resize',] }],
4502
+ closeDetails: [{ type: i0.HostListener, args: ['document:keydown.escape',] }]
3353
4503
  };
3354
4504
 
3355
4505
  var PrincipalDashboardLibV2Module = /** @class */ (function () {
@@ -3367,7 +4517,9 @@
3367
4517
  TableviewComponent,
3368
4518
  PrincipalDashboardLibV2Component,
3369
4519
  FullTableViewComponent,
3370
- SmallTableViewComponent
4520
+ SmallTableViewComponent,
4521
+ TimelineComponent,
4522
+ InsightsComponent
3371
4523
  ],
3372
4524
  imports: [
3373
4525
  common.CommonModule,
@@ -3399,6 +4551,8 @@
3399
4551
  exports.ɵf = TableviewComponent;
3400
4552
  exports.ɵg = FullTableViewComponent;
3401
4553
  exports.ɵh = SmallTableViewComponent;
4554
+ exports.ɵi = TimelineComponent;
4555
+ exports.ɵj = InsightsComponent;
3402
4556
 
3403
4557
  Object.defineProperty(exports, '__esModule', { value: true });
3404
4558