src/app/modules/dashboard/components/organization/organization.component.ts
The organization component
Display organization creation, consumption dashboard data
OnDestroy
AfterViewInit
selector | app-organization |
styleUrls | ./organization.component.scss |
templateUrl | ./organization.component.html |
constructor(downloadService: DownloadService, route: Router, activatedRoute: ActivatedRoute, userService: UserService, searchService: SearchService, rendererService: RendererService, orgService: OrganisationService, resourceService: ResourceService, toasterService: ToasterService, navigationhelperService: NavigationHelperService)
|
||||||||||||||||||||||||||||||||||||||||||||
Default method of OrganisationService class
Parameters :
|
downloadReport |
downloadReport()
|
Download dashboard report
Returns :
void
|
getDashboardData | ||||||||||||
getDashboardData(timePeriod: string, identifier: string)
|
||||||||||||
Function to get dashboard data for given time period and organization identifier
Parameters :
Example :
Returns :
void
|
getMyOrganisations |
getMyOrganisations()
|
Get logged user organization ids list
Returns :
void
|
getOrgDetails | ||||||||
getOrgDetails(orgIds: string[])
|
||||||||
To get organization details.
Parameters :
Example :
Returns :
void
|
graphNavigation | ||||||||
graphNavigation(step: string)
|
||||||||
To change graph - from Number of user per day to Time spent by day and vice versa
Parameters :
Example :
Returns :
void
|
ngAfterViewInit |
ngAfterViewInit()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
onAfterDatasetChange | ||||||||
onAfterDatasetChange(datasetType: string)
|
||||||||
To change dashboard type
Parameters :
Example :
Returns :
boolean
|
onAfterFilterChange | ||||||||
onAfterFilterChange(timePeriod: string)
|
||||||||
Change time period filter. As of now dashboard supports only to show last 7 days, 14 days, and 5 weeks data.
Parameters :
Example :
Returns :
boolean
|
onAfterOrgChange | ||||||||||||
onAfterOrgChange(identifier: string, orgName: string)
|
||||||||||||
To change organization selection
Parameters :
Example :
Returns :
boolean
|
setError | ||||||||
setError(flag: boolean)
|
||||||||
To set error
Parameters :
Example :
Returns :
void
|
validateIdentifier | ||||||||
validateIdentifier(identifier: string | string)
|
||||||||
This function is used to validate given organization identifier. User gets redirect to home page if url contains invalid identifier or valid identifier but logged-in user is not a member of given identifier
Parameters :
Example :
Returns :
void
|
Public activatedRoute |
Type : ActivatedRoute
|
To get params from url |
blockData |
Type : string[]
|
Contains dashboard block data |
chartType |
Type : string
|
Default value : 'line'
|
Chart type |
datasetType |
Type : string
|
Default value : 'creation'
|
Dataset type |
disabledClass |
Default value : false
|
Disabled class |
Public downloadService |
Type : DownloadService
|
Download report service |
graphData |
Type : any
|
Contains course consumption line chart data |
identifier |
Type : string
|
Default value : ''
|
Contains selected course identifier Identifier is needed to construct dashboard api url |
interactObject |
Type : any
|
isMultipleOrgs |
Default value : false
|
To show / hide organization dropdwon |
lineChartLegend |
Default value : true
|
To display graph legend |
myOrganizations |
Type : Array<any>
|
Default value : []
|
Organization list |
Public navigationhelperService |
Type : NavigationHelperService
|
orgService |
Type : OrganisationService
|
To get org dashboard data by making api call |
rendererService |
Type : RendererService
|
Chart renderer to call chart service like Line chart service Currently it supports only line and bar chart |
resourceService |
Type : ResourceService
|
To get language constant |
Public route |
Type : Router
|
Router to change url |
searchService |
Type : SearchService
|
To get logged-in user published course(s) |
SelectedOrg |
Type : string
|
Selected organization |
showDashboard |
Default value : false
|
To show dashboard canvas |
showDownloadSuccessModal |
Default value : false
|
To show download successful confirmation modal |
showGraph |
Type : number
|
Default value : 1
|
Contains Graph index to switch between two graphs |
showLoader |
Default value : true
|
To show / hide loader |
subscription |
Type : Subscription
|
telemetryImpression |
Type : IImpressionEventInput
|
telemetryImpression object for org admin dashboard page |
timePeriod |
Type : string
|
Default value : '7d'
|
Contains time period - last 7days, 14days, and 5weeks |
Public toasterService |
Type : ToasterService
|
Public unsubscribe |
Default value : new Subject<void>()
|
Variable to gather and unsubscribe all observable subscriptions in this component. |
userDataSubscription |
Type : Subscription
|
userService |
Type : UserService
|
To get logged-in user profile |
import {takeUntil, first} from 'rxjs/operators';
import { Component, OnDestroy, AfterViewInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription , Subject } from 'rxjs';
import { RendererService, OrganisationService, DownloadService } from './../../services';
import { UserService, SearchService } from '@sunbird/core';
import { ResourceService, ServerResponse, ToasterService, NavigationHelperService } from '@sunbird/shared';
import { DashboardData } from './../../interfaces';
import { IImpressionEventInput } from '@sunbird/telemetry';
import * as _ from 'lodash-es';
/**
* The organization component
*
* Display organization creation, consumption dashboard data
*/
@Component({
selector: 'app-organization',
templateUrl: './organization.component.html',
styleUrls: ['./organization.component.scss']
})
/**
* @class OrganisationComponent
*/
export class OrganisationComponent implements OnDestroy, AfterViewInit {
/**
* Variable to gather and unsubscribe all observable subscriptions in this component.
*/
public unsubscribe = new Subject<void>();
interactObject: any;
/**
* Contains time period - last 7days, 14days, and 5weeks
*/
timePeriod = '7d';
/**
* Contains selected course identifier
*
* Identifier is needed to construct dashboard api url
*/
identifier = '';
/**
* Dataset type
*/
datasetType = 'creation';
userDataSubscription: Subscription;
/**
* Contains course consumption line chart data
*/
graphData: any;
/**
* Contains dashboard block data
*/
blockData: string[];
/**
* Contains Graph index to switch between two graphs
*/
showGraph = 1;
/**
* Organization list
*/
myOrganizations: Array<any> = [];
/**
* Selected organization
*/
SelectedOrg: string;
/**
* To display graph legend
*/
lineChartLegend = true;
/**
* Chart type
*/
chartType = 'line';
/**
* To show / hide loader
*/
showLoader = true;
/**
* To show dashboard canvas
*/
showDashboard = false;
/**
* To show / hide organization dropdwon
*/
isMultipleOrgs = false;
/**
* Disabled class
*/
disabledClass = false;
/**
* To show download successful confirmation modal
*/
showDownloadSuccessModal = false;
/**
* Download report service
*/
public downloadService: DownloadService;
/**
* Router to change url
*/
public route: Router;
/**
* To get params from url
*/
public activatedRoute: ActivatedRoute;
/**
* To get logged-in user published course(s)
*/
searchService: SearchService;
/**
* Chart renderer to call chart service like Line chart service
*
* Currently it supports only line and bar chart
*/
rendererService: RendererService;
/**
* To get language constant
*/
resourceService: ResourceService;
/**
* To get org dashboard data by making api call
*/
orgService: OrganisationService;
/**
* To get logged-in user profile
*/
userService: UserService;
/**
* telemetryImpression object for org admin dashboard page
*/
telemetryImpression: IImpressionEventInput;
subscription: Subscription;
/**
* Default method of OrganisationService class
*
* @param {DownloadService} downloadService To make download report api call
* @param {Router} route Url navigation
* @param {ActivatedRoute} activatedRoute To get params from url
* @param {UserService} userService To get logged-in user profile
* @param {SearchService} searchService To get organization details
* @param {RendererService} rendererService To get chart service
* @param {OrganisationService} orgService To get dashboard data
* @param {ResourceService} resourceService To get language constant
*/
constructor(downloadService: DownloadService, route: Router, activatedRoute: ActivatedRoute, userService: UserService,
searchService: SearchService, rendererService: RendererService, orgService: OrganisationService, resourceService: ResourceService,
public toasterService: ToasterService, public navigationhelperService: NavigationHelperService) {
this.downloadService = downloadService;
this.activatedRoute = activatedRoute;
this.searchService = searchService;
this.rendererService = rendererService;
this.resourceService = resourceService;
this.orgService = orgService;
this.userService = userService;
this.route = route;
this.activatedRoute.params.subscribe(params => {
if (params.id && params.timePeriod) {
// this.datasetType = params.datasetType;
this.showDashboard = false;
this.interactObject = { id: params.id, type: 'Organization', ver: '1.0' };
this.getDashboardData(params.timePeriod, params.id);
}
});
this.getMyOrganisations();
}
/**
* Function to get dashboard data for given time period and organization identifier
*
* @param {string} timePeriod timePeriod: last 7d/14d/5w
* @param {string} identifier organization unique identifier
*
* @example getDashboardData(7d, do_xxxxx)
*/
getDashboardData(timePeriod: string, identifier: string) {
this.showLoader = true;
this.isMultipleOrgs = false;
this.timePeriod = timePeriod;
this.identifier = identifier;
const params = {
data: {
identifier: this.identifier,
timePeriod: this.timePeriod
},
dataset: this.datasetType === 'creation' ? 'ORG_CREATION' : 'ORG_CONSUMPTION'
};
this.orgService.getDashboardData(params).pipe(
takeUntil(this.unsubscribe))
.subscribe(
(data: DashboardData) => {
this.blockData = data.numericData;
this.graphData = this.rendererService.visualizer(data, this.chartType);
this.showDashboard = true;
this.setError(false);
},
err => {
this.setError(true);
this.toasterService.error(`Root org doesn't exist for this Organization Id and channel`);
}
);
}
/**
* This function is used to validate given organization identifier.
*
* User gets redirect to home page if url contains invalid identifier or
* valid identifier but logged-in user is not a member of given identifier
*
* @param {string} identifier organization unique identifier
*
* @example validateIdentifier(do_xxxxx)
*/
validateIdentifier(identifier: string | '') {
if (identifier) {
const selectedOrg = _.find(this.myOrganizations, ['id', identifier]);
if (selectedOrg && selectedOrg.id) {
this.SelectedOrg = selectedOrg.orgName;
} else {
// TODO: Need to redirect to home page
this.route.navigate(['groups']);
}
}
}
/**
* Change time period filter.
*
* As of now dashboard supports only to show last 7 days, 14 days, and 5 weeks data.
*
* @param {string} timePeriod timePeriod: last 7d / 14d / 5w
*
* @example onAfterFilterChange(7d)
*/
onAfterFilterChange(timePeriod: string) {
if (this.timePeriod === timePeriod) {
return false;
}
this.route.navigate(['dashBoard/organization', this.datasetType, this.identifier, timePeriod]);
}
/**
* To change dashboard type
*
* @param {string} datasetType creation and consumption
*
* @example onAfterDatasetChange(creation)
*/
onAfterDatasetChange(datasetType: string) {
if (this.datasetType === datasetType) {
return false;
}
this.showGraph = datasetType === 'creation' ? 1 : 0;
this.route.navigate(['dashBoard/organization', datasetType, this.identifier, this.timePeriod]);
}
/**
* To change graph - from Number of user per day to Time spent by day and vice versa
*
* @param {string} step next / previous
*
* @example graphNavigation(next)
*/
graphNavigation(step: string) {
step === 'next' ? this.showGraph++ : this.showGraph--;
}
/**
* To change organization selection
*
* @param {string} identifier organization identifier
* @param {string} orgName organization name
*
* @example onAfterOrgChange(identifier: do_xxxxx, Test Organization)
*/
onAfterOrgChange(identifier: string, orgName: string) {
if (this.identifier === identifier) {
return false;
}
this.route.navigate(['dashBoard/organization', this.datasetType, identifier, this.timePeriod]);
}
/**
* To set error
*
* @param {boolean} flag show error
*
* @example setError(true)
*/
setError(flag: boolean) {
this.showLoader = false;
}
/**
* Get logged user organization ids list
*/
getMyOrganisations(): void {
const data = this.searchService.searchedOrganisationList;
if (data && data.content && data.content.length) {
this.myOrganizations = data.content;
if (this.myOrganizations.length === 1) {
this.identifier = this.myOrganizations[0].id;
this.route.navigate(['dashBoard/organization', this.datasetType, this.identifier, this.timePeriod]);
}
this.isMultipleOrgs = this.userService.userProfile.organisationIds.length > 1 ? true : false;
this.showLoader = false;
this.validateIdentifier(this.identifier);
} else {
this.userDataSubscription = this.userService.userData$.pipe(first()).subscribe(
user => {
if (user && user.userProfile.organisationIds && user.userProfile.organisationIds.length) {
this.getOrgDetails(user.userProfile.organisationIds);
} else {
// this.route.navigate(['groups']);
}
},
err => {
this.setError(true);
}
);
}
}
/**
* Download dashboard report
*/
downloadReport() {
this.disabledClass = true;
const option = {
data: { identifier: this.identifier, timePeriod: this.timePeriod },
dataset: this.datasetType === 'creation' ? 'ORG_CREATION' : 'ORG_CONSUMPTION'
};
this.downloadService.getReport(option).pipe(
takeUntil(this.unsubscribe))
.subscribe(
(data: ServerResponse) => {
this.showDownloadSuccessModal = true;
this.disabledClass = false;
},
err => {
this.disabledClass = false;
}
);
}
/**
* To get organization details.
*
* @param {string[]} orgIds org id list
*
* @example getOrgDetails([do_xxxxx])
*/
getOrgDetails(orgIds: string[]) {
if (orgIds && orgIds.length) {
this.searchService.getOrganisationDetails({ orgid: orgIds }).pipe(
takeUntil(this.unsubscribe))
.subscribe(
(data: ServerResponse) => {
if (data.result.response.content) {
this.myOrganizations = data.result.response.content;
this.isMultipleOrgs = orgIds.length > 1 ? true : false;
if (this.myOrganizations.length === 1) {
this.identifier = this.myOrganizations[0].id;
this.route.navigate(['dashBoard/organization', this.datasetType, this.identifier, this.timePeriod]);
}
}
if (this.identifier) {
this.isMultipleOrgs = false;
this.validateIdentifier(this.identifier);
}
this.showLoader = false;
},
(err: ServerResponse) => {
this.setError(true);
}
);
}
}
ngAfterViewInit () {
const params = this.activatedRoute.snapshot.params;
setTimeout(() => {
this.telemetryImpression = {
context: {
env: this.activatedRoute.snapshot.data.telemetry.env
},
edata: {
uri: 'dashboard/organization/' + params.datasetType
+ '/' + params.id + '/' + params.timePeriod,
type: this.activatedRoute.snapshot.data.telemetry.type,
pageid: this.activatedRoute.snapshot.data.telemetry.pageid,
duration: this.navigationhelperService.getPageLoadTime()
},
object: {
id: params.id,
type: 'org',
ver: '1.0'
}
};
});
}
ngOnDestroy() {
if (this.userDataSubscription) {
this.userDataSubscription.unsubscribe();
}
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
<div class="ui container">
<div class="ui grid " [hidden]="showLoader">
<div class="twelve wide column">
<div class="ui grid mt-20">
<div class="nine wide column">
<!-- If user is member of only one organization -->
<span class="dashBoardSectionHeading pr-10" *ngIf="myOrganizations && myOrganizations.length >= 1">
<i class="bar chart icon"></i>
</span>
<span class="dashBoardSectionHeading" *ngIf="myOrganizations && myOrganizations.length === 1">
{{myOrganizations[0].orgName}}
</span>
<!-- If user is member of multiple organization -->
<div class="ui dropdown " suiDropdown *ngIf="myOrganizations && myOrganizations.length > 1">
<div class="text dashBoardSectionHeading">{{SelectedOrg ||
resourceService?.frmelmnts?.lbl?.dashboardselectorg}}
<span>
<i class="dropdown icon"></i>
</span>
</div>
<div class="menu" suiDropdownMenu>
<div class="item" [ngClass]="{'active': organization.id === identifier }"
*ngFor="let organization of myOrganizations" tabindex="0"
(click)="onAfterOrgChange(organization.id, organization.orgName); SelectedOrg = organization.orgName;"
appTelemetryInteract
[telemetryInteractObject]="{ id: organization.id, type: 'organization', ver: '1.0' }"
[telemetryInteractEdata]="{id:'orgDropdown',type:'click',pageid:'org-admin-dashboard'}">
{{organization.orgName}}
</div>
</div>
</div>
</div>
<!-- Dataset types - Creation, Consumption -->
<!-- <div class="right floated three wide column padding-right-dropdown pr-10" *ngIf="identifier && myOrganizations && myOrganizations.length > 0">
<div class="ui dropdown float-ContentRight" suiDropdown>
<div class="text dashBoardSectionHeading">
{{datasetType === 'creation' ? resourceService?.frmelmnts?.lbl?.creationdataset : resourceService?.frmelmnts?.lbl?.consumptiondataset}}
</div>
<span class="dashBoardSectionHeading">
<i class="dropdown icon"></i>
</span>
<div class="menu" suiDropdownMenu>
<div class="active item" [ngClass]="{'active': datasetType === 'creation' }" (click)="onAfterDatasetChange('creation'); SelectedDataSet = 'Creation'"
appTelemetryInteract [telemetryInteractObject]="interactObject" [telemetryInteractEdata]="{id:'creation',type:'click',pageid:'org-admin-dashboard'}">Creation</div>
<div class="item" [ngClass]="{'active': datasetType === 'consumption' }" (click)="onAfterDatasetChange('consumption'); SelectedDataSet = 'Consumption'"
appTelemetryInteract [telemetryInteractObject]="interactObject" [telemetryInteractEdata]="{id:'consumption',type:'click',pageid:'org-admin-dashboard'}">Consumption</div>
</div>
</div>
</div> -->
<div class="right floated three wide column padding-right-dropdown pr-10"
*ngIf="identifier && myOrganizations && myOrganizations.length > 0">
<div class="ui dropdown float-ContentRight">
<div class="text dashBoardSectionHeading">
{{resourceService?.frmelmnts?.lbl?.creationdataset}}
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="ui grid" *ngIf="identifier">
<div class="twelve wide column" *ngIf="myOrganizations && myOrganizations.length > 0"
[appTelemetryImpression]="telemetryImpression">
<span class="dashBoardMenuItem cursor-pointer active" tabindex="0" (click)="onAfterFilterChange('7d')"
[ngClass]="{'active': timePeriod === '7d' }" appTelemetryInteract [telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'7d',type:'click',pageid:'org-admin-dashboard'}">
{{resourceService?.frmelmnts?.lbl?.dashboardsevendaysfilter}}
</span>
<span class="dashBoardMenuItem cursor-pointer" tabindex="0" (click)="onAfterFilterChange('14d')"
[ngClass]="{'active': timePeriod === '14d' }" appTelemetryInteract
[telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'14d',type:'click',pageid:'org-admin-dashboard'}">
{{resourceService?.frmelmnts?.lbl?.dashboardfourteendaysfilter}}
</span>
<span class="dashBoardMenuItem cursor-pointer" tabindex="0" (click)="onAfterFilterChange('5w')"
[ngClass]="{'active': timePeriod === '5w' }" appTelemetryInteract [telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'5w',type:'click',pageid:'org-admin-dashboard'}">
{{resourceService?.frmelmnts?.lbl?.dashboardfiveweeksfilter}}
</span>
</div>
</div>
<div *ngIf="myOrganizations && myOrganizations.length > 0">
<div class="ui warning message tweleve wide column mt-20" *ngIf="!identifier && !showLoader"
[ngStyle]="{'display': showDashboard ? 'none' : '' }">
<div class="header" [appTelemetryImpression]="telemetryImpression">
{{resourceService?.frmelmnts?.lbl?.dashboardnoorgselected}}
</div>
{{resourceService?.frmelmnts?.lbl?.dashboardnoorgselecteddesc}}
</div>
</div>
<div *ngIf="showLoader" class="mt-20">
<app-loader></app-loader>
</div>
<div *ngIf="showDashboard && myOrganizations && myOrganizations.length > 0"
[ngStyle]="{'display': showDashboard === true ? 'block' : 'none' }">
<div class="consumption-data-holder mt-20" [appTelemetryImpression]="telemetryImpression">
<div class="ui grid">
<div class="four wide column" *ngFor="let block of blockData">
<div class="ui card adminDashboardCard">
<div class="content center aligned">
<div class="meta ">{{block.name}}</div>
<h2 class="description dashboardCardSubText">{{block.value}}</h2>
</div>
</div>
</div>
</div>
</div>
<div class="ui one column grid mt-20" *ngIf="graphData && graphData.length > 0"
[ngStyle]="{'display': showDashboard === true ? 'block' : 'none' }">
<div class="column">
<div class="regular slider pt-30 course-graph-holder">
<div *ngFor="let block of graphData;let graphIndex = index;">
<div [hidden]="showGraph === graphIndex">
<div class="ui segment">
<canvas baseChart width="300" height="90" [datasets]="block.yaxesData" [labels]="block.xaxesData"
[options]="block.chartOptions" [colors]="block.chartColors" [legend]="true"
[chartType]="chartType">
</canvas>
</div>
</div>
</div>
<div class="corse-graph-navigation-btn" *ngIf="graphData && graphData.length-1 > 0">
<button class="ui primary basic compact icon button" tabindex="0" (click)="graphNavigation('previous')"
[ngClass]="{'disabled': showGraph <= 0 }" appTelemetryInteract
[telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'previous',type:'click',pageid:'org-admin-dashboard'}">
<i class="left arrow icon"></i>
</button>
<button class="ui primary basic compact icon button" tabindex="0" (click)="graphNavigation('next')"
[ngClass]="{'disabled': showGraph >= graphData.length - 1 }" appTelemetryInteract
[telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'next',type:'click',pageid:'org-admin-dashboard'}">
<i class="right arrow icon"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Download-->
<div class="twelve wide column mt-20" *ngIf="graphData && graphData.length > 0">
<p class="text-align-right dashBoardCardText">
{{resourceService?.frmelmnts?.instn?.t0058}}
<a [ngClass]="{'disabledDownloadButton': disabledClass }" class="item text-decoration-underline pl-10"
(click)="downloadReport();" tabindex="0" appTelemetryInteract [telemetryInteractObject]="interactObject"
[telemetryInteractEdata]="{id:'download',type:'click',pageid:'org-admin-dashboard'}">
{{resourceService?.frmelmnts?.instn?.t0059}}</a>
</p>
</div>
<app-modal-wrapper *ngIf="showDownloadSuccessModal"
[config]="{disableClose: false, size: 'small', panelClass: 'material-modal'}"
(dismiss)="showDownloadSuccessModal = false">
<ng-template sbModalContent>
<div class="sb-mat__modal sb-success">
<!--Header-->
<div mat-dialog-title class="mb-0">
<div class="title" tabindex="0">{{resourceService?.frmelmnts?.instn?.t0060}}</div>
<button aria-label="close dialog" mat-dialog-close class="close-btn"></button>
</div>
<!--/Header-->
<!--Content-->
<div class="sb-mat__modal__content">
<p>{{resourceService?.messages?.stmsg?.m0095}}</p>
</div>
<!--/Content-->
<mat-dialog-actions class="sb-mat__modal__actions"></mat-dialog-actions>
</div>
</ng-template>
</app-modal-wrapper>
</div>
</div>
</div>
</div>
./organization.component.scss
@use "@project-sunbird/sb-styles/assets/mixins/mixins" as *;
.ui.card.adminDashboardCard {
width: calculateRem(400px);
}
.ui.dropdown .menu {
width: 100%;
}
span.dashBoardMenuItem.active {
color: var(--primary-color);
font-weight: bold;
}
.dashBoardMenuItem {
font-weight: 500;
font-size: 0.9375em;
color: var(--primary-color);
}
.ui.cards > .card .meta.dashBoardCardText {
font-weight: 500;
font-size: 1.1em;
color: var(--rc-7c7b7b);
}
.ui.cards > .card > .content > .description.dashboardCardSubText {
font-weight: 500;
font-size: 1.6em;
color: var(--gray-800);
}
.dashBoardSectionHeading {
font-weight: bold;
font-size: 1.4em;
}
.text-align-right {
text-align: right !important;
}
.ui.card.adminDashboardCard {
width: calculateRem(330px);
}
.corse-graph-navigation-btn {
position: absolute;
right: 1.6em;
top: 1.6em;
}
.course-graph-holder {
display: inline;
}