Commit 9fb7b93f authored by Marco Ortiz's avatar Marco Ortiz

Merge branch 'developer-dashboard' into 'developer'

Developer dashboard

See merge request ByteBot/web/bytebot-workspace!3
parents 592c0764 be08330d
...@@ -1824,15 +1824,23 @@ ...@@ -1824,15 +1824,23 @@
} }
}, },
"@xdf/gallery": { "@xdf/gallery": {
"version": "file:../../XDF/ng-byte-framework/dist/xdf-gallery/xdf-gallery-1.0.8.tgz", "version": "file:../../XDF/ng-byte-framework/dist/xdf-gallery/xdf-gallery-1.0.15.tgz",
"integrity": "sha512-eOUgs9LGPAVJh/pPBwbNfv+3ZlIlmm7YgO01lFzZUD9bzETOxIfj3DpOluRE4WZxL2UBLB+c3uYjczwoEnEPlg==", "integrity": "sha512-N+Lca09t72pCmqDBTWD4wMq3EfO5Yh5dhLNUh9GcsnDs4/e3wwoxYOEXVAdNyojpCg6hud3tBoGmr7IR17mhuQ==",
"requires": {
"tslib": "^1.9.0"
}
},
"@xdf/graph": {
"version": "file:../../Cuenta corriente/ccb-workspace/dist/xdf-graph/xdf-graph-0.0.1.tgz",
"integrity": "sha512-Am44vHFa3yrknhS0nzvfHXZIZDuNJq2iKNU6jlcT7HOVdaiVH4cHrHaBxB8OP6u7QaZR3BKi+rUoHHhYNFKzUw==",
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }
}, },
"@xdf/layouts": { "@xdf/layouts": {
"version": "file:../../XDF/ng-byte-framework/dist/xdf-layouts/xdf-layouts-1.0.1.tgz", "version": "1.0.3",
"integrity": "sha512-yF8ay/2Yc2/o3AjBIcNkMIY0PmKD/OEfu1GHVymfVUWp3F5BrTXYo3Lvy1Nz/E+GEN9aDtGEkyrZe0zpzO+Irg==", "resolved": "http://192.168.27.7:4873/@xdf%2flayouts/-/layouts-1.0.3.tgz",
"integrity": "sha512-bLcyoQpl3Xk7QSz8jnX/G4RdhuUfOI3+htwZ5iag0dly/tjY3+LclDNMNIRnYo0QVRTYBQQiClOTG2QT0/hWJg==",
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }
...@@ -2022,6 +2030,15 @@ ...@@ -2022,6 +2030,15 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true "dev": true
}, },
"angular-gauge-chart": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/angular-gauge-chart/-/angular-gauge-chart-0.7.2.tgz",
"integrity": "sha512-KVyf6suQiDrcc7ncjhdXKX23S0INgm4Rv5H2W1zXJoYQXZ7VQ1y742u/yH6faZw1rfBpC2KBx2IqRnBHXWL6Gw==",
"requires": {
"gauge-chart": "^0.5.1",
"tslib": "^1.9.0"
}
},
"angular-highcharts": { "angular-highcharts": {
"version": "9.0.11", "version": "9.0.11",
"resolved": "https://registry.npmjs.org/angular-highcharts/-/angular-highcharts-9.0.11.tgz", "resolved": "https://registry.npmjs.org/angular-highcharts/-/angular-highcharts-9.0.11.tgz",
...@@ -4459,6 +4476,15 @@ ...@@ -4459,6 +4476,15 @@
"d3-time-format": "2" "d3-time-format": "2"
} }
}, },
"d3-scale-chromatic": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
"integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
"requires": {
"d3-color": "1",
"d3-interpolate": "1"
}
},
"d3-selection": { "d3-selection": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
...@@ -6488,6 +6514,15 @@ ...@@ -6488,6 +6514,15 @@
"resolved": "http://192.168.27.7:4873/function-bind/-/function-bind-1.1.1.tgz", "resolved": "http://192.168.27.7:4873/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"gauge-chart": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/gauge-chart/-/gauge-chart-0.5.3.tgz",
"integrity": "sha512-AsupJVuWToUa/3hEp6Q9IRGUQZKyeCBBRRZGJKhZvfsx31QXdPsmifew+L9r5x5TbxXwcrPpaBm7RLBX0p6CMw==",
"requires": {
"d3": "^4.13.0",
"d3-scale-chromatic": "^1.1.1"
}
},
"genfun": { "genfun": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "http://192.168.27.7:4873/genfun/-/genfun-5.0.0.tgz", "resolved": "http://192.168.27.7:4873/genfun/-/genfun-5.0.0.tgz",
......
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { BaseLayoutComponent, NotFoundComponent, CustomLayoutComponent } from '@xdf/layouts'; import { BaseLayoutComponent, CustomLayoutComponent, NotFoundComponent } from '@xdf/layouts';
import { AuthGuard, LoginComponent } from '@xdf/security'; import { AuthGuard, LoginComponent } from '@xdf/security';
import { BytebotLayoutComponent } from './modules/bytebot-layout/bytebot-layout/bytebot-layout.component';
import { HomeComponent } from './views/home/home.component'; import { HomeComponent } from './views/home/home.component';
const routes: Routes = [ const routes: Routes = [
// Remover para SSO // Remover para SSO
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
// Main redirect // Main redirect
{ path: '', redirectTo: 'home', pathMatch: 'full', canActivate: [AuthGuard] }, { path: '', redirectTo: 'home', pathMatch: 'full'},
{ {
path: '', component: CustomLayoutComponent, path: '', component: CustomLayoutComponent,
children: [ children: [
{ path: 'home', component: HomeComponent, data: { breadcrumb: 'Home' } }, { path: 'home', component: HomeComponent, data: { breadcrumb: 'Home' } }
{ ]
path: 'security', data: { breadcrumb: 'Seguridad' }, canLoad: [AuthGuard], },
loadChildren: () => import('./modules/security/security.module').then(m => m.SecurityModule) { path: '', component: BaseLayoutComponent,
}, children: [
{
path: 'settings', data: { breadcrumb: 'Ajustes Generales' }, canLoad: [AuthGuard],
loadChildren: () => import('./modules/settings/settings.module').then(m => m.SettingsModule)
},
{ {
path: 'configuration', data: { breadcrumb: 'Agentes' }, canLoad: [AuthGuard], path: 'dashboards', data: { breadcrumb: 'Dashboards' },
loadChildren: () => import('./modules/agent/agent.module').then(m => m.AgentModule) loadChildren: () => import('./modules/dashboards/dashboards.module').then(m => m.DashboardsModule)
}, },
], ]
canActivate: [AuthGuard]
}, },
{ path: 'notpermitted', component: NotFoundComponent}, { path: 'notpermitted', component: NotFoundComponent},
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
...@@ -37,6 +37,8 @@ import { ...@@ -37,6 +37,8 @@ import {
} from '@angular/material-moment-adapter'; } from '@angular/material-moment-adapter';
import { CustomProgramsFakeBackendInterceptor } from './interceptors/custom-programs-fake-backend.interceptor'; import { CustomProgramsFakeBackendInterceptor } from './interceptors/custom-programs-fake-backend.interceptor';
import { AgentFakeBackendInterceptor } from './interceptors/agent-fake-backend.interceptor'; import { AgentFakeBackendInterceptor } from './interceptors/agent-fake-backend.interceptor';
import { OperativeDashboardFakeBackendInterceptor } from './interceptors/operative-dashboard-fake-backend.interceptor';
import { CustomErrorHandlerInterceptor } from './interceptors/custom-error-handler.interceptor';
const INITIAL_LANGUAGE = 'es'; const INITIAL_LANGUAGE = 'es';
...@@ -111,19 +113,20 @@ export function createTranslateLoader(http: HttpClient) { ...@@ -111,19 +113,20 @@ export function createTranslateLoader(http: HttpClient) {
{ provide: ResourceAuthGuard, useClass: ResourceAuthGuard }, { provide: ResourceAuthGuard, useClass: ResourceAuthGuard },
// descomentar estas lineas para OAUTH // descomentar estas lineas para OAUTH
{ provide: AuthGuard, useClass: OAuthGuard }, // { provide: AuthGuard, useClass: OAuthGuard },
{ provide: AuthenticationService, useClass: OAuthAuthenticationService }, // { provide: AuthenticationService, useClass: OAuthAuthenticationService },
{ provide: APP_INITIALIZER, useFactory: loginLoaderFactory, deps: [AuthenticationService], multi: true }, // { provide: APP_INITIALIZER, useFactory: loginLoaderFactory, deps: [AuthenticationService], multi: true },
// Para probar mantenimientos // Para probar mantenimientos
// comentar estas lineas para OAUTH // comentar estas lineas para OAUTH
// { provide: AuthGuard, useClass: AuthGuard}, { provide: AuthGuard, useClass: AuthGuard},
// { provide: AuthenticationService, useClass: ByteAuthenticationService }, { provide: AuthenticationService, useClass: ByteAuthenticationService },
// { provide: HTTP_INTERCEPTORS, useClass: AuthenticationFakeBackendInterceptor, multi: true}, { provide: HTTP_INTERCEPTORS, useClass: AuthenticationFakeBackendInterceptor, multi: true},
{ provide: HTTP_INTERCEPTORS, useClass: SettingsFakeBackendInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: SettingsFakeBackendInterceptor, multi: true },
//{ provide: HTTP_INTERCEPTORS, useClass: CustomProgramsFakeBackendInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: CustomProgramsFakeBackendInterceptor, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: AgentFakeBackendInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: AgentFakeBackendInterceptor, multi: true },
//{ provide: HTTP_INTERCEPTORS, useClass: OperativeDashboardFakeBackendInterceptor, multi: true },
{ provide: APP_INITIALIZER, useFactory: init_app, deps: [InitCommonsService, TranslateService], multi: true } { provide: APP_INITIALIZER, useFactory: init_app, deps: [InitCommonsService, TranslateService], multi: true }
......
import {
HttpRequest,
HttpResponse,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, of, EMPTY } from 'rxjs';
import { Injectable } from '@angular/core';
import { catchError, tap, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { NotificationService, NotificationType, AuthenticationService } from '@xdf/commons';
import { Router } from '@angular/router';
import { ConflictErrorDialogService } from '@xdf/gallery';
@Injectable()
export class CustomErrorHandlerInterceptor implements HttpInterceptor {
constructor(
private router: Router,
private translate: TranslateService,
private authenticationService: AuthenticationService,
private notificationService: NotificationService,
private conflictErrorDialogService: ConflictErrorDialogService) {
}
intercept(
req: HttpRequest<any>, next: HttpHandler): Observable<any> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
switch (error.status) {
case 401:
const message = this.translate.instant('message.error.unauthorized');
this.notificationService.showMessage(message, this.translate.instant('title.error'), NotificationType.error);
break;
case 404:
const messageError = error.error ? error.error : error.message;
this.notificationService.showMessage(messageError, this.translate.instant('title.error'), NotificationType.error);
break;
case 409:
const messageDuplicate = this.translate.instant('message.error.duplicated');
this.notificationService.showMessage(messageDuplicate, this.translate.instant('title.error'), NotificationType.error);
break;
case 419:// validar cual es el código correcto
this.conflictErrorDialogService.loadComponent(
null,
'title.error.conflict',
'message.error.conflict',
error.error);
break;
default:
if (error.status === 0) {
this.authenticationService.login(null, null).subscribe(
data => {
window.location.href = './';
});
} else {
let message = '';
if (error.error) {
if (error.error.params) {
const params = [];
error.error.params.forEach(element => {
params.push(this.translate.instant(element));
});
message = this.translate.instant(error.error.message, params);
} else {
if (error.error.message) {
message = this.translate.instant(error.error.message);
} else {
message = this.translate.instant(error.error);
}
}
} else {
message = this.translate.instant(error.message);
}
this.notificationService.showMessage(message, this.translate.instant('title.error'), NotificationType.error);
}
}
return throwError(error);
})
);
}
}
\ No newline at end of file
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import * as source from '../../assets/fake-data/operative-dashboard-data.json';
const basePath = '/test';
@Injectable()
export class OperativeDashboardFakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
const data = (source as any).default;
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(50))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
case url.match('.*' + basePath) && method === 'POST':
return getData();
default:
// pass through any requests not handled above
return next.handle(request);
}
}
function getData() {
return ok(data);
}
// helper functions
function ok(bodyContent?) {
return of(new HttpResponse({ status: 200, body: bodyContent }));
}
function error(message: string) {
return throwError({ error: { message } });
}
}
}
File mode changed from 100644 to 100755
/*
* Inspinia js helpers:
*
* correctHeight() - fix the height of main wrapper
* detectBody() - detect windows size
* smoothlyMenu() - add smooth fade in/out on navigation show/ide
*
*/
// import * as jQuery_ from 'jquery';
// const jQuery = jQuery_;
declare var jQuery: any;
export function correctHeight() {
const pageWrapper = jQuery('#page-wrapper');
const navbarHeight = jQuery('nav.navbar-default').height();
const wrapperHeight = pageWrapper.height();
if (navbarHeight > wrapperHeight) {
pageWrapper.css('min-height', navbarHeight + 'px');
}
if (navbarHeight <= wrapperHeight) {
if (navbarHeight < jQuery(window).height()) {
pageWrapper.css('min-height', jQuery(window).height() + 'px');
} else {
pageWrapper.css('min-height', navbarHeight + 'px');
}
}
if (jQuery('body').hasClass('fixed-nav')) {
if (navbarHeight > wrapperHeight) {
pageWrapper.css('min-height', navbarHeight + 'px');
} else {
pageWrapper.css('min-height', jQuery(window).height() - 60 + 'px');
}
}
}
export function detectBody() {
if (jQuery(document).width() < 769) {
jQuery('body').addClass('body-small');
} else {
jQuery('body').removeClass('body-small');
}
}
export function smoothlyMenu() {
if (!jQuery('body').hasClass('mini-navbar') || jQuery('body').hasClass('body-small')) {
// Hide menu in order to smoothly turn on when maximize menu
jQuery('#side-menu').hide();
// For smoothly turn on menu
setTimeout(
() => {
jQuery('#side-menu').fadeIn(400);
}, 200);
} else if (jQuery('body').hasClass('fixed-sidebar')) {
jQuery('#side-menu').hide();
setTimeout(
() => {
jQuery('#side-menu').fadeIn(400);
}, 100);
} else {
// Remove all inline style from jquery fadeIn function to reset menu state
jQuery('#side-menu').removeAttr('style');
}
}
export function fixHeight() {
const heightWithoutNavbar = jQuery('body > #wrapper').height() - 62;
jQuery('.sidebar-panel').css('min-height', heightWithoutNavbar + 'px');
const navbarheight = jQuery('nav.navbar-default').height();
const wrapperHeight = jQuery('#page-wrapper').height();
if (navbarheight > wrapperHeight) {
jQuery('#page-wrapper').css('min-height', navbarheight + 'px');
}
if (navbarheight < wrapperHeight) {
jQuery('#page-wrapper').css('min-height', jQuery(window).height() + 'px');
}
if (jQuery('body').hasClass('fixed-nav')) {
if (navbarheight > wrapperHeight) {
jQuery('#page-wrapper').css('min-height', navbarheight + 'px');
} else {
jQuery('#page-wrapper').css('min-height', jQuery(window).height() - 60 + 'px');
}
}
}
import { NgModule } from '@angular/core';
import { DirtyGuard, XdfGalleryModule } from '@xdf/gallery';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { XdfSettingsModule } from '@xdf/settings';
import { BytebotLayoutComponent } from './bytebot-layout/bytebot-layout.component';
import { LayoutModule } from '@angular/cdk/layout';
@NgModule({
declarations: [
BytebotLayoutComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
LayoutModule
],
entryComponents:[
],
exports: [
BytebotLayoutComponent
]
,
providers: [
{ provide: DirtyGuard, useClass: DirtyGuard }
]
})
export class BytebotLayoutModule { }
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BytebotLayoutComponent } from './bytebot-layout.component';
describe('BytebotLayoutComponent', () => {
let component: BytebotLayoutComponent;
let fixture: ComponentFixture<BytebotLayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BytebotLayoutComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BytebotLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, HostListener, OnInit } from '@angular/core';
import { detectBody } from '../app.helpers';
@Component({
selector: 'byte-bytebot-layout',
templateUrl: './bytebot-layout.component.html',
styleUrls: ['./bytebot-layout.component.scss']
})
export class BytebotLayoutComponent implements OnInit {
constructor(//protected toogleService: ToogleService
) { }
ngOnInit() {
detectBody();
}
@HostListener('window:resize')
public onResize() {
detectBody();
}
gotoTop() {
console.log('custom-layout');
}
onToggle() {
console.log("test");
//this.toogleService.onToggle(true);
}
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { OperativeDashboardComponent } from './views/operative-dashboard/components/operative-dashboard/operative-dashboard.component';
import { CustomerInteractionComponent } from './views/customer-interaction-dashboard/componentes/customer-interaction/customer-interaction.component';
const routes: Routes = [
{
path: 'operative', component: OperativeDashboardComponent,
data: {
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Operativo',
title: 'dashboards.operative.title',
titleDesc: 'dashboards.operative.title.desc'
}
},
{
path: 'customer-interaction', component: CustomerInteractionComponent,
data: {
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Operativo',
title: 'dashboards.customer.interaction.title',
titleDesc: 'dashboards.customer.interaction.title.desc'
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardsRoutingModule { }
import { NgModule } from '@angular/core';
import { DirtyGuard, XdfGalleryModule, EditableDataTableTemplateResolver } from '@xdf/gallery';
import { DashboardsRoutingModule } from './dashboards-routing.module';
import { TranslateModule } from '@ngx-translate/core';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MatFormFieldModule, MatSelectModule, MatTooltipModule, MatButtonModule, MatDatepickerModule, MatInputModule, MatPaginatorModule, MatSortModule, MatIconModule } from '@angular/material';
import { XdfSettingsModule } from '@xdf/settings';
import { XdfLayoutsModule } from '@xdf/layouts';
import { OperativeDashboardComponent } from './views/operative-dashboard/components/operative-dashboard/operative-dashboard.component';
import { SummaryPetiWidgetComponent } from './views/operative-dashboard/components/summary-peti-widget/summary-peti-widget.component';
import { CustomerActivityWidgetComponent } from './views/operative-dashboard/components/customer-activity-widget/customer-activity-widget.component';
import { ChartsModule } from 'ng2-charts';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { ChartModule, HIGHCHARTS_MODULES } from 'angular-highcharts';
import more from 'highcharts/highcharts-more.src';
import exporting from 'highcharts/modules/exporting.src';
import highmaps from 'highcharts/modules/map.src';
import sankey from 'highcharts/modules/sankey.src';
import { XdfGraphModule } from '@xdf/graph';
import { WidgetPetiComponent } from './views/operative-dashboard/components/widget-peti/widget-peti.component';
import { GaugeChartComponent } from './views/operative-dashboard/components/gauge-chart/gauge-chart.component';
import { GaugeChartModule } from 'angular-gauge-chart';
import { InactivitySesionComponent } from './views/operative-dashboard/components/inactivity-sesion/inactivity-sesion.component';
import { HeatMapComponent } from './views/operative-dashboard/components/heat-map/heat-map.component';
import { OperativeDashboardFilterComponent } from './views/operative-dashboard/components/operative-dashboard-filter/operative-dashboard-filter.component'
import { NgxMatDateAdapter, NgxMatDatetimePickerModule, NgxMatNativeDateModule, NgxMatTimepickerModule } from '@angular-material-components/datetime-picker';
import { LocalizedDateModule } from '@xdf/commons';
import { StackedColumnChartComponent } from './views/customer-interaction-dashboard/componentes/stacked-column-chart/stacked-column-chart.component';
import { CustomerInteractionComponent } from './views/customer-interaction-dashboard/componentes/customer-interaction/customer-interaction.component';
import { CustomerInteractionFilterComponent } from './views/customer-interaction-dashboard/componentes/customer-interaction-filter/customer-interaction-filter.component';
import { SankeyDiagramComponent } from './views/customer-interaction-dashboard/componentes/sankey-diagram/sankey-diagram.component';
import { MatTabsModule } from '@angular/material/tabs';
import { MessageByIntentTableComponent } from './views/customer-interaction-dashboard/componentes/message-by-intent-table/message-by-intent-table.component';
import { UnidentifiedSentencesTableComponent } from './views/customer-interaction-dashboard/componentes/unidentified-sentences-table/unidentified-sentences-table.component';
import { MatTableModule } from '@angular/material/table';
import { AvgIntentByCustomerComponent } from './views/customer-interaction-dashboard/componentes/avg-intent-by-customer/avg-intent-by-customer.component';
import { TracingIntentByCustomerComponent } from './views/customer-interaction-dashboard/componentes/tracing-intent-by-customer/tracing-intent-by-customer.component';
import { MillisToDayHourMinuteSecond } from './pipes/millis-to-day-hour-minute-second.pipe';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
export function highchartsModules() {
// apply Highcharts Modules to this array
return [ more, exporting, highmaps, sankey];
}
@NgModule({
declarations: [
OperativeDashboardComponent,
SummaryPetiWidgetComponent,
CustomerActivityWidgetComponent,
WidgetPetiComponent,
GaugeChartComponent,
InactivitySesionComponent,
HeatMapComponent,
OperativeDashboardFilterComponent,
StackedColumnChartComponent,
CustomerInteractionComponent,
CustomerInteractionFilterComponent,
SankeyDiagramComponent,
MessageByIntentTableComponent,
UnidentifiedSentencesTableComponent,
AvgIntentByCustomerComponent,
MillisToDayHourMinuteSecond,
TracingIntentByCustomerComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
DashboardsRoutingModule,
MatFormFieldModule,
MatSelectModule,
MatTooltipModule,
XdfGalleryModule,
XdfSettingsModule,
XdfLayoutsModule,
MatButtonModule,
ChartModule,
XdfGraphModule,
GaugeChartModule,
NgxChartsModule,
ChartsModule,
MatDatepickerModule,
NgxMatDatetimePickerModule,
NgxMatTimepickerModule,
NgxMatNativeDateModule,
MatInputModule,
LocalizedDateModule,
MatTabsModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatIconModule,
NgbModule
],
entryComponents: [
],
providers: [
{ provide: DirtyGuard, useClass: DirtyGuard },
EditableDataTableTemplateResolver,
{ provide: HIGHCHARTS_MODULES, useFactory: highchartsModules }
]
})
export class DashboardsModule { }
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Pipe({
name: 'millisToDayHourMinuteSecond'
})
export class MillisToDayHourMinuteSecond implements PipeTransform {
constructor(private translateService: TranslateService) {}
transform(value: any, ...args: any[]) {
let valueNumber: number;
let type = typeof value;
if (type === 'number') {
valueNumber = value;
} else if (type === 'string') {
valueNumber = + value;
}
if (valueNumber > 0) {
let days = Math.trunc(valueNumber / 86400);
valueNumber = valueNumber % 86400;
let hours = Math.trunc(valueNumber / 3600);
valueNumber = valueNumber % 3600;
let minutes = Math.trunc(valueNumber / 60);
valueNumber = valueNumber % 60;
let seconds = valueNumber;
return (days < 10 ? '0' : '') + days + this.translateService.instant("pipe.format.day")
+ ' ' + (hours < 10 ? '0' : '') + hours + this.translateService.instant("pipe.format.hour")
+ ' ' + (minutes < 10 ? '0' : '') + minutes + this.translateService.instant("pipe.format.minute")
+ ' ' + (seconds < 10 ? '0' : '') + seconds + this.translateService.instant("pipe.format.seconds");
}
return value;
}
}
\ No newline at end of file
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CustomerInteractionService {
constructor(protected http: HttpClient) {
}
get serviceURL(): string {
return './dashboard/customer-interaction';
}
getSummary(startDate:Date, endDate:Date, channel:string) {
return this.http.post(this.serviceURL, {startDate, endDate, channel});
}
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class OperationalConsultationService {
constructor(protected http: HttpClient) {
}
get serviceURL(): string {
return './dashboard/operative';
}
getSummary(startDate:Date, endDate:Date, channel:string) {
return this.http.post(this.serviceURL, {startDate, endDate, channel});
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AvgIntentByCustomerComponent } from './avg-intent-by-customer.component';
describe('AvgIntentByCustomerComponent', () => {
let component: AvgIntentByCustomerComponent;
let fixture: ComponentFixture<AvgIntentByCustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AvgIntentByCustomerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AvgIntentByCustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit } from '@angular/core';
import { Chart } from 'angular-highcharts';
import * as Highcharts from 'highcharts';
import { utc } from 'moment';
@Component({
selector: 'byte-avg-intent-by-customer',
templateUrl: './avg-intent-by-customer.component.html',
styleUrls: ['./avg-intent-by-customer.component.scss']
})
export class AvgIntentByCustomerComponent implements OnInit {
chart: Chart;
@Input()
data: Array<number> = [];
@Input()
minDate: Date = new Date();
constructor() { }
ngOnInit() {
if (!this.data) {
return;
}
this.chart = new Chart({
title: {
text: ''
},
exporting: {
enabled: false
},
credits: {
enabled: false
},
legend: {
enabled: false
},
chart: {
renderTo: 'container',
type: 'column',
zoomType: 'xy'
},
xAxis: {
type: 'datetime',
tickInterval: 86400000,
labels: {
formatter: function() {
return Highcharts.dateFormat('%e - %b',
this.value);
}
}
},
yAxis: {
title: {
text: ''
}
},
series: [
{
name: 'Promedio',
pointStart: this.getDate(this.minDate),
pointInterval: 86400000,
dataLabels: {
inside: false,
enabled: true,
style: {
color: 'white'
}
},
data: this.data
}
] as Array<any>
});
}
private getDate(date: Date): any {
// let dateNumber = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDay(), 0, 0, 0);
// console.log(dateNumber);
// return dateNumber;
return (new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0))).getTime();
}
}
<div class="row">
<div class="col-12">
<div class="mail-box-content consult pb-3" style="margin: -15px -15px 15px -15px;">
<div class="expansion-content pull-right">
<div class="row">
<div class="col-5">
<mat-form-field class="full-width">
<mat-label>{{'query.filters.startDate' | translate }}</mat-label>
<input matInput [ngxMatDatetimePicker]="startPicker" [(ngModel)]="startDateTime"
id="startPickerCIP" (click)="startPicker.open()" onkeyup="this.value=this.value.replace(/.*/gi, '');">
<mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #startPicker></ngx-mat-datetime-picker>
</mat-form-field>
</div>
<div class="col-4">
<mat-form-field class="full-width">
<mat-label>{{'query.filters.endDate' | translate }}</mat-label>
<input matInput [ngxMatDatetimePicker]="endPicker" [(ngModel)]="endDateTime"
(click)="endPicker.open()" onkeyup="this.value=this.value.replace(/.*/gi, '');">
<mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #endPicker></ngx-mat-datetime-picker>
</mat-form-field>
</div>
<div class="col-3">
<button class="btn btn-primary btn-sm ng-star-inserted" style="margin-left: 10px;
margin-top: 10px !important;"
type="button" [disabled]="!startDateTime || !endDateTime" (click)="search()">
<i class="fa fa-search"></i><span class="btn-label">Buscar</span>
</button>
</div>
</div>
</div>
<div class="pull-left icon" *ngIf="icon">
<div [innerHTML]="icon"></div>
</div>
<h3 class="grid-title">
<span>{{ header | translate }}</span>
</h3>
<span *ngIf="headerDesc" class="small">{{ headerDesc | translate }}</span>
</div>
</div>
</div>
<router-outlet></router-outlet>
\ No newline at end of file
.ibox-content {
font-size: 12px;
}
.table.invoice-total {
margin-bottom: 0px;
}
.grid-title {
margin-top: 2px !important;
}
.mat-form-field + .mat-form-field {
margin-left: 8px;
}
.expansion-content {
padding-top: 5px;
}
.mail-box-content.consult {
padding: 10px 20px;
}
.mail-box-content {
h3 {
margin: 0;
font-weight: 500;
}
}
.icon {
padding: 5px 10px 0 0;
}
.debit {
color: red;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomerInteractionFilterComponent } from './customer-interaction-filter.component';
describe('CustomerInteractionFilterComponent', () => {
let component: CustomerInteractionFilterComponent;
let fixture: ComponentFixture<CustomerInteractionFilterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CustomerInteractionFilterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CustomerInteractionFilterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { NgxMatDatetimePicker } from '@angular-material-components/datetime-picker';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { SharedFilterService } from 'projects/bytebot-html/src/app/services/shared-filters.service';
@Component({
selector: 'byte-customer-interaction-filter',
templateUrl: './customer-interaction-filter.component.html',
styleUrls: ['./customer-interaction-filter.component.scss']
})
export class CustomerInteractionFilterComponent implements OnInit {
form: FormGroup;
channels = [
{
value: "W",
label: "Whatsapp"
},
{
value: "F",
label: "Facebook"
}
];
endDateTime: Date;
startDateTime: Date;
selectedChannel: string;
isLoading: Boolean = false;
@Input()
header: string;
@Input()
headerDesc: string;
@Input()
icon: string = '<i class="fa fa-bar-chart-o"></i>';
@Output()
onFilter: EventEmitter<any> = new EventEmitter();
constructor(
protected route: ActivatedRoute,
protected router: Router,
protected sharedFilterService: SharedFilterService) {
}
ngOnInit() {
const title = this.route.snapshot.data['title'];
const titleDesc = this.route.snapshot.data['titleDesc'];
this.header = title;
this.headerDesc = titleDesc;
this.form = new FormGroup({
id: new FormControl('', Validators.required),
period: new FormControl('', Validators.required)
});
this.endDateTime = new Date();
this.startDateTime = this.getStartDate(this.endDateTime);
this.search();
}
search() {
const filters = {startDateTime: this.startDateTime, endDateTime: this.endDateTime, channel: this.selectedChannel}
this.onFilter.emit(filters);
}
private getStartDate(endDate: Date): Date {
return new Date(Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), 5, 0, 0));
}
keyup(event) {
event.preventDefault();
}
clickEvent (event: NgxMatDatetimePicker<any>) {
event.open();
}
}
<byte-customer-interaction-filter (onFilter)="filter($event)"></byte-customer-interaction-filter>
<ng-container *ngIf="isLoading">
<div class="spiner-example">
<div class="sk-spinner sk-spinner-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</div>
</ng-container>
<ng-container *ngIf="!isLoading && isDataLoaded">
<!-- <div class="ibox" style="padding-top: 15px;">
<div class="ibox-content b-s no-padding sankey-ibox" style="text-align: center;">
<div class="row">
<div class="col-5 sankey-diagram">
<byte-sankey-diagram [data]="data?.sessionFlow?.intent"></byte-sankey-diagram>
</div>
<div class="col-7 sankey-table">
<byte-message-by-intent-table [minDate]="startDate" [maxDate]="endDate"></byte-message-by-intent-table>
</div>
</div>
</div>
</div> -->
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="intentCustomer">
<div class="row sankey-ibox">
<div class="col-5 sankey-diagram">
<byte-sankey-diagram [data]="data?.sessionFlow?.intent"></byte-sankey-diagram>
</div>
<div class="col-7 sankey-table">
<byte-message-by-intent-table [minDate]="startDate" [maxDate]="endDate"></byte-message-by-intent-table>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="sentencesCustomer">
<div class="row sankey-ibox">
<div class="col-6 sankey-table-left">
<byte-unidentified-sentences-table [minDate]="startDate" [maxDate]="endDate"></byte-unidentified-sentences-table>
</div>
<div class="col-6 sankey-diagram-right">
<byte-sankey-diagram [data]="data?.sessionFlow?.sentence"></byte-sankey-diagram>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="tracingPanelOptions">
<div class="row">
<div class="col-12">
<byte-tracing-intent-by-customer [data]="data?.summaryIntents" [minDate]="startDate" [maxDate]="endDate"></byte-tracing-intent-by-customer>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="avgIntentOptions">
<div class="row">
<div class="col-12">
<byte-avg-intent-by-customer [data]="data?.intentAvgByCustomer" [minDate]="startDate"></byte-avg-intent-by-customer>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="goalPanelOptions">
<div class="row">
<div class="col-12">
<byte-stacked-column-chart [data]="data?.summaryGoals"></byte-stacked-column-chart>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
</ng-container>
\ No newline at end of file
.ibox {
margin-bottom: 15px !important;
}
.b-s {
box-shadow: 0 0 13px 0 rgba(62, 44, 90, 0.08);
// border: 1px solid rgba(0, 0, 0, 0.09);
}
.no-padding {
padding: 0 !important;
}
.sankey-ibox {
.sankey-table {
padding-left: 0 !important;
}
.sankey-table-left {
padding-right: 0 !important;
border-right: 1px solid #e7eaec !important;
}
.sankey-diagram{
padding-right: 0 !important;
border-right: 1px solid #e7eaec !important;
}
.sankey-diagram-right{
padding-left: 0 !important;
border-right: 1px solid #e7eaec !important;
}
}
::ng-deep xdf-widget-panel .panel-toolbar button {
display: none !important;
}
::ng-deep xdf-widget-panel .panel-container {
background-color: #fff !important;
}
::ng-deep.navbar-header {
margin-bottom: 10px;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomerInteractionComponent } from './customer-interaction.component';
describe('CustomerInteractionComponent', () => {
let component: CustomerInteractionComponent;
let fixture: ComponentFixture<CustomerInteractionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CustomerInteractionComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CustomerInteractionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { ThemePalette } from '@angular/material';
import { NotificationService, NotificationType } from '@xdf/commons';
import { CustomerInteractionService } from '../../../../services/customer-interaction.service';
@Component({
selector: 'byte-customer-interaction',
templateUrl: './customer-interaction.component.html',
styleUrls: ['./customer-interaction.component.scss']
})
export class CustomerInteractionComponent implements OnInit {
links = ['Graph', 'Detail'];
activeLink = this.links[0];
latestDate = new Date();
existDateRange: Boolean = false;
isLoading: Boolean = false;
startDate: Date;
endDate: Date;
data: any;
isDataLoaded: Boolean = false;
background: ThemePalette = undefined;
constructor(
protected customerInteractionService: CustomerInteractionService,
protected notificationService: NotificationService
) { }
ngOnInit() {
}
toggleBackground() {
this.background = this.background ? undefined : 'primary';
}
addLink() {
this.links.push(`Link ${this.links.length + 1}`);
}
panelOptions = {
elevation: false,
title: {
primary: 'Mensajes por horario de actividad del cliente',
secondary: ''
}
}
tracingPanelOptions = {
elevation: false,
title: {
primary: 'dashboards.customer.interaction.tracing.intent',
secondary: ''
}
}
avgIntentOptions = {
elevation: false,
title: {
primary: 'dashboards.customer.interaction.avg.intent',
secondary: ''
}
}
goalPanelOptions = {
elevation: false,
title: {
primary: 'dashboards.customer.interaction.goals',
secondary: ''
}
}
intentCustomer = {
elevation: false,
title: {
primary: 'dashboards.customer.interaction.intents',
secondary: ''
}
};
sentencesCustomer = {
elevation: false,
title: {
primary: 'dashboards.customer.interaction.sentences',
secondary: ''
}
};
filter(filters) {
this.isDataLoaded = false;
this.isLoading = true;
this.startDate = filters['startDateTime'];
this.endDate = filters['endDateTime'];
const channel = filters['channel'];
this.customerInteractionService.getSummary(this.startDate, this.endDate, channel).subscribe(response => {
this.data = response;
this.isLoading = false;
this.isDataLoaded = this.isValidData(this.data);
if (!this.isDataLoaded) {
this.notificationService.showMessage("No se encontró información para el rango de fechas seleccionado", "", NotificationType.error);
}
}, err => {
this.isLoading = false;
});
}
private isValidData(data:any): Boolean {
if (data['sessionFlow'] && data['sessionFlow']['intent'] && data['sessionFlow']['sentence']) {
if (data['sessionFlow']['intent']['total'] > 0 || data['sessionFlow']['sentence']['total'] > 0) {
return true;
}
}
return false;
}
}
<table mat-table [dataSource]="dataSource" matSort [matSortActive]="sortColumn" matSortDisableClear
[matSortDirection]="sortDirection" class="crud-table table" style="width: 100%; margin-bottom: 0;">
<ng-container matColumnDef="sentence">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Frase
<mat-icon *ngIf="sortColumn === 'sentence'">
<i class="fa fa-sort-amount-desc" *ngIf="sortDirection === 'desc'"></i>
<i class="fa fa-sort-amount-asc" *ngIf="sortDirection === 'asc'"></i>
</mat-icon>
</th>
<td mat-cell *matCellDef="let element" style="text-align: left; width: 300px;"
[matTooltip]="element.sentence.length > 30 ? element.sentence: null" triggers="hover">
{{ (element.sentence.length>30)? (element.sentence | slice:0:30)+'...':(element.sentence) }}
</td>
</ng-container>
<ng-container matColumnDef="identifier">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Intención
<mat-icon *ngIf="sortColumn === 'identifier'">
<i class="fa fa-sort-amount-desc" *ngIf="sortDirection === 'desc'"></i>
<i class="fa fa-sort-amount-asc" *ngIf="sortDirection === 'asc'"></i>
</mat-icon>
</th>
<td mat-cell *matCellDef="let element" style="text-align: left;">
{{ (element.identifier.length>30)? (element.identifier | slice:0:30)+'...':(element.identifier) }} </td>
</ng-container>
<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Ocurrencia
<mat-icon *ngIf="sortColumn === 'count'">
<i class="fa fa-sort-amount-desc" *ngIf="sortDirection === 'desc'"></i>
<i class="fa fa-sort-amount-asc" *ngIf="sortDirection === 'asc'"></i>
</mat-icon>
</th>
<td mat-cell *matCellDef="let element"> {{element.count}} </td>
</ng-container>
<ng-container matColumnDef="customer">
<th mat-header-cell *matHeaderCellDef> Clientes </th>
<td mat-cell *matCellDef="let element"> {{element.customerCount}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator [length]="pagination?.totalItems" [pageSize]="pagination?.itemsPerPage"
[pageSizeOptions]="[8, 10, 15]" [pageIndex]="pagination?.currentPage">
</mat-paginator>
\ No newline at end of file
$fontcolor: #676a6c;
.content {
width: 100%;
display: flex;
padding-right: 0px !important;
.grid-container, .filter-panel {
.crud-table {
margin-bottom: 0px;
width: 100%;
}
}
.filter-panel {
margin-bottom: 10px;
}
.toolbar-option {
margin-left: 10px;
margin-right: -10px;
}
}
.grid-ibox-content {
// width: calc(100% - 35px);
width: 100%;
}
.grid-ibox-content-options {
width: calc(100% - 35px);
}
.btn-actions {
.btn {
font-size: 10px;
white-space: nowrap;
}
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container mat-spinner {
margin: -10px auto 0 auto;
}
th.mat-column-actions {
width: 1px !important;
padding-right: 0px !important;
}
td.mat-column-actions {
padding-right: 20px !important;
cursor: default !important;
}
td {
vertical-align: middle !important;
color: $fontcolor;
font-size: 12px;
}
th {
vertical-align: middle !important;
}
tr.mat-header-row {
height: 50px !important;
}
tr.mat-footer-row, tr.mat-row:not(.inner-detail-row) {
height: 40px;
}
.table-toolbar {
right: 10px;
top: 10px;
button {
font-size: 12px;
}
}
.status-button-bar {
button {
font-size: 16px;
}
padding-top: 8px;
padding-left: 15px;
}
.mat-raised-button {
padding: 0px 10px;
}
.icon-centered-button span.mat-button-wrapper {
display: flex;
}
.icon-centered-button mat-icon {
font-size: 15px;
padding-top: 2px;
}
::ng-deep .mat-sort-header-arrow {
visibility: hidden;
}
::ng-deep .mat-sort-header-button {
.mat-icon {
padding-left: 10px;
font-size: 12px;
padding-top: 5px;
}
}
@media (min-width: 576px) {
::ng-deep .d-sm-block {
display: table-cell !important;
}
}
@media (max-width: 576px) {
::ng-deep .mat-paginator-page-size-label {
display: none !important;
}
}
tr.inner-detail-row {
height: 0;
.mat-column-expandedDetail {
padding: 0 10px;
}
}
.inner-element-detail {
overflow: hidden;
display: flex;
}
//@extend
tr.inner-detail-row {
td {
border-bottom: 1px solid #dee2e6;
border-top: 0px;
}
}
tr.inner-element-row.odd, tr.inner-detail-row.odd {
background-color: rgba(0, 0, 0, 0.05);
}
// tr.inner-detail-row {
// cursor: pointer !important;
// }
tr.mat-row-auth:not(.inner-expanded-row):hover {
background-color:#f8f9fa;
cursor: pointer !important;
}
tr.mat-row-auth.inner-expanded-row:hover {
cursor: pointer !important;
}
tr.inner-element-row-expanded td {
border-top: 0px;
border-bottom: 0px;
}
.additional-options-section {
padding: 0;
width: 100%;
display: inline-grid;
button {
margin: 1px 0;
color: inherit;
font-size: inherit;
border-radius: 0px;
}
}
::ng-deep .mat-menu-item {
padding: 0 10px;
}
@media (min-width: 992px) {
.d-t-lg-block {
display: table-cell !important;
}
}
th {
background: #f2f2f2;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MessageByIntentTableComponent } from './message-by-intent-table.component';
describe('MessageByIntentTableComponent', () => {
let component: MessageByIntentTableComponent;
let fixture: ComponentFixture<MessageByIntentTableComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MessageByIntentTableComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MessageByIntentTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { DataSource } from '@angular/cdk/table';
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatSort } from '@angular/material';
import { Pagination, SortField } from '@xdf/commons';
import { DynaDataSource } from '@xdf/gallery';
import { tap } from 'rxjs/operators';
import { MessageByIntentService } from './services/message-by-intent.service';
@Component({
selector: 'byte-message-by-intent-table',
templateUrl: './message-by-intent-table.component.html',
styleUrls: ['./message-by-intent-table.component.scss']
})
export class MessageByIntentTableComponent implements OnInit {
displayedColumns: string[] = ['sentence', 'identifier', 'count', 'customer'];
dataSource: DynaDataSource;
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
@ViewChild(MatSort, { static: false }) sort: MatSort;
sortColumn: string = "count";
sortDirection: string = "desc";
statusQuickFilter: string;
pagingSize = 8;
@Input()
minDate: Date;
@Input()
maxDate: Date;
pagination: Pagination;
constructor(protected service: MessageByIntentService) { }
ngOnInit() {
this.dataSource = new DynaDataSource(this.service);
// Paginación
this.pagination = new Pagination();
this.pagination.currentPage = 0;
this.pagination.itemsPerPage = this.pagingSize;
this.pagination.filterExpression = "startDate=" + this.minDate.getTime() + "&&endDate=" + this.maxDate.getTime();
// Ordenación por defecto
if (this.sortColumn && this.sortDirection) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection;
sortField.field = this.sortColumn;
this.pagination.sortFields.push(sortField);
}
this.dataSource.load(this.pagination);
}
ngAfterViewInit(): void {
this.sort.sortChange.subscribe(() => {
if (!this.sort.disabled && this.sort.active) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection = this.sort.direction;
sortField.field = this.sortColumn = this.sort.active;
this.pagination.sortFields.push(sortField);
}
this.paginator.pageIndex = 0;
this.dataSource.load(this.pagination);
});
this.paginator.page
.pipe(
tap(() => {
this.pagination.currentPage = this.paginator.pageIndex;
this.pagination.itemsPerPage = this.paginator.pageSize;
this.dataSource.load(this.pagination);
})
)
.subscribe();
}
disabledPopper(value: number): Boolean {
return value <= 30;
}
}
export interface MessageByIntent{
sentence: string;
intent: string;
ocurrency: number;
customers: number;
}
\ No newline at end of file
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Pagination } from '@xdf/commons';
import { DynaDataService } from '@xdf/gallery';
import { DataService } from '@xdf/gallery/lib/views/crud/services/data.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class MessageByIntentService extends DynaDataService {
serviceURL = "./dashboard/customer-interaction/message-by-intent";
constructor(private httpClient: HttpClient) {
super(httpClient);
}
getResultPagination(pagination: Pagination): Observable<Pagination> {
return this.httpClient.post(this.serviceURL + '/page', pagination).pipe(map(data => this.getPage(data as Pagination)));
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SankeyDiagramComponent } from './sankey-diagram.component';
describe('SankeyDiagramComponent', () => {
let component: SankeyDiagramComponent;
let fixture: ComponentFixture<SankeyDiagramComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SankeyDiagramComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SankeyDiagramComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit } from '@angular/core';
import { Chart } from 'angular-highcharts';
@Component({
selector: 'byte-sankey-diagram',
templateUrl: './sankey-diagram.component.html',
styleUrls: ['./sankey-diagram.component.scss']
})
export class SankeyDiagramComponent implements OnInit {
chart: Chart;
startPoint:string = "Session start";
private percentDetail = {};
@Input()
data;
constructor() { }
ngOnInit() {
if (!this.data) {
return;
}
let dataDiagram = this.generateData(this.data);
if (!dataDiagram) {
return;
}
this.chart = new Chart({
title: {
text: ''
},
exporting: {
enabled: false
},
chart: {
marginTop: 30
},
credits: {
enabled: false
},
accessibility: {
point: {
valueDescriptionFormat: '{index}. {point.from} to {point.to}, {point.weight}.'
}
},
// tooltip: {
// formatter: function() {
// return update(this.key);
// }
// },
series: [{
marker: {
symbol: 'url(https://www.highcharts.com/samples/graphics/sun.png)'
},
lineWidth: 5,
keys: ['from', 'to', 'weight'],
data: dataDiagram,
type: 'sankey',
name: 'Intenciones',
// dataLabels: {
// formatter: function () {
// console.log(this);
// return "";
// }
// },
}] as Array<any>
});
}
private generateData(data: any) {
if (!data['topList']) {
return undefined;
}
let dataForDiagram = [];
data['topList'].forEach(x => {
//PercentageInventary.getInstance().setPercent(x['identifier'], x['count']/data['total']*100);
dataForDiagram.push([this.startPoint, x['identifier'], x['count']]);
});
return dataForDiagram;
}
}
function update(key) {
return PercentageInventary.getInstance().getPercent(key);
}
export class PercentageInventary {
private percentages: any = {};
private static instance: PercentageInventary;
public static getInstance(): PercentageInventary {
if (!PercentageInventary.instance) {
PercentageInventary.instance = new PercentageInventary();
}
return PercentageInventary.instance;
}
getPercent(name: string) {
if (this.percentages[name]) {
return (this.percentages[name] as number).toFixed(2) + "%";
}
return null;
}
setPercent(name: string, value: any) {
this.percentages[name] = value;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { StackedColumnChartComponent } from './stacked-column-chart.component';
describe('StackedColumnChartComponent', () => {
let component: StackedColumnChartComponent;
let fixture: ComponentFixture<StackedColumnChartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ StackedColumnChartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StackedColumnChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit } from '@angular/core';
import { Chart } from 'angular-highcharts';
import * as Highcharts from 'highcharts';
@Component({
selector: 'byte-stacked-column-chart',
templateUrl: './stacked-column-chart.component.html',
styleUrls: ['./stacked-column-chart.component.scss']
})
export class StackedColumnChartComponent implements OnInit {
chart: Chart;
@Input()
data: any;
constructor() { }
ngOnInit() {
if (!this.data) {
return;
}
let categories = this.generateData(this.data, 'goal');
let dataSeries = this.generateData(this.data, 'count');
this.chart = new Chart({
chart: {
type: 'column'
},
exporting: {
enabled: false
},
legend: {
enabled: false
},
title: {
text: ''
},
xAxis: {
categories: categories
},
credits: {
enabled: false
},
yAxis: {
labels: {
enabled: false
},
title: {
text: ''
}
},
tooltip: {
headerFormat: '<b>{point.x}</b><br/>',
pointFormat: '{point.stackTotal}'
},
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: false
}
}
},
series: [{
dataLabels: {
inside: false,
enabled: true,
style: {
color: 'white'
}
},
name: 'John',
data: dataSeries,
linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
stops: [
[0, '#1ab394'], // start
[1, '#FFFFFF'] // end
]
}] as Array<any>
});
}
private generateData(data: Array<any>, field: string) {
let dataGenerated = [];
data.forEach(x => {
dataGenerated.push(x[field]);
});
return dataGenerated;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TracingIntentByCustomerComponent } from './tracing-intent-by-customer.component';
describe('TracingIntentByCustomerComponent', () => {
let component: TracingIntentByCustomerComponent;
let fixture: ComponentFixture<TracingIntentByCustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TracingIntentByCustomerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TracingIntentByCustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit } from '@angular/core';
import { Chart } from 'angular-highcharts';
import * as Highcharts from 'highcharts';
@Component({
selector: 'byte-tracing-intent-by-customer',
templateUrl: './tracing-intent-by-customer.component.html',
styleUrls: ['./tracing-intent-by-customer.component.scss']
})
export class TracingIntentByCustomerComponent implements OnInit {
chart: Chart;
@Input()
minDate: Date;
@Input()
maxDate: Date;
@Input()
data: any;
constructor() { }
ngOnInit() {
console.log(this.minDate);
console.log(this.maxDate);
let series: Array<any> = this.getGenerateData(this.data);
this.chart = new Chart({
chart: {
type: 'line'
},
credits: {
enabled: false
},
title: {
text: ''
},
subtitle: {
text: null
},
exporting: {
enabled: false
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
itemMarginTop: 10,
itemMarginBottom: 10
},
xAxis: {
type: 'datetime',
min: this.getDate(this.minDate),
max: this.getDate(this.maxDate),
labels: {
formatter: function() {
return Highcharts.dateFormat('%e - %b',
this.value * 1000);
}
}
},
yAxis: {
title: {
text: ''
},
},
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%e - %b',
this.x * 1000) +
' date, ' + this.y;
}
},
plotOptions: {
line: {
marker: {
enabled: true
}
}
},
series: series
});
}
private getGenerateData(data: any): Array<any> {
let series: Array<any> = [];
for (const intent in data) {
let serie = {};
serie['name'] = intent;
serie['data'] = data[intent];
series.push(serie);
console.log(serie);
}
return series;
}
private getDate(date: Date) {
console.log(date);
let dateNumber = date.getTime();
dateNumber = dateNumber / 1000;
return Math.trunc(dateNumber);
}
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Pagination } from '@xdf/commons';
import { DynaDataService } from '@xdf/gallery';
import { DataService } from '@xdf/gallery/lib/views/crud/services/data.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class UnidentifiedSentenceService extends DynaDataService {
serviceURL = "./dashboard/operative/sentence-by-intent";
constructor(private httpClient: HttpClient) {
super(httpClient);
}
getResultPagination(pagination: Pagination): Observable<Pagination> {
return this.httpClient.post(this.serviceURL + '/page', pagination).pipe(map(data => this.getPage(data as Pagination)));
}
}
\ No newline at end of file
<table mat-table [dataSource]="dataSource" matSort [matSortActive]="sortColumn" [matSortDirection]="sortDirection" matSortDisableClear
class="crud-table table" style="width: 100%;margin-bottom: 0;">
<ng-container matColumnDef="identifier">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Frase
<mat-icon *ngIf="sortColumn === 'identifier'">
<i class="fa fa-sort-amount-desc"
*ngIf="sortDirection === 'desc'"></i>
<i class="fa fa-sort-amount-asc"
*ngIf="sortDirection === 'asc'"></i>
</mat-icon>
</th>
<td mat-cell *matCellDef="let element" style="text-align: left; width: 300px;"> {{ (element.identifier.length>30)? (element.identifier | slice:0:30)+'...':(element.identifier) }} </td>
</ng-container>
<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Ocurrencia
<mat-icon *ngIf="sortColumn === 'count'">
<i class="fa fa-sort-amount-desc"
*ngIf="sortDirection === 'desc'"></i>
<i class="fa fa-sort-amount-asc"
*ngIf="sortDirection === 'asc'"></i>
</mat-icon>
</th>
<td mat-cell *matCellDef="let element"> {{element.count}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator [length]="pagination?.totalItems" [pageSize]="pagination?.itemsPerPage"
[pageSizeOptions]="[8, 10, 15]" [pageIndex]="pagination?.currentPage">
</mat-paginator>
\ No newline at end of file
$fontcolor: #676a6c;
.content {
width: 100%;
display: flex;
padding-right: 0px !important;
.grid-container, .filter-panel {
.crud-table {
margin-bottom: 0px;
width: 100%;
}
}
.filter-panel {
margin-bottom: 10px;
}
.toolbar-option {
margin-left: 10px;
margin-right: -10px;
}
}
.grid-ibox-content {
// width: calc(100% - 35px);
width: 100%;
}
.grid-ibox-content-options {
width: calc(100% - 35px);
}
.btn-actions {
.btn {
font-size: 10px;
white-space: nowrap;
}
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container mat-spinner {
margin: -10px auto 0 auto;
}
th.mat-column-actions {
width: 1px !important;
padding-right: 0px !important;
}
td.mat-column-actions {
padding-right: 20px !important;
cursor: default !important;
}
td {
vertical-align: middle !important;
color: $fontcolor;
font-size: 12px;
}
th {
vertical-align: middle !important;
}
tr.mat-header-row {
height: 50px !important;
}
tr.mat-footer-row, tr.mat-row:not(.inner-detail-row) {
height: 40px;
}
.table-toolbar {
right: 10px;
top: 10px;
button {
font-size: 12px;
}
}
.status-button-bar {
button {
font-size: 16px;
}
padding-top: 8px;
padding-left: 15px;
}
.mat-raised-button {
padding: 0px 10px;
}
.icon-centered-button span.mat-button-wrapper {
display: flex;
}
.icon-centered-button mat-icon {
font-size: 15px;
padding-top: 2px;
}
::ng-deep .mat-sort-header-arrow {
visibility: hidden;
}
::ng-deep .mat-sort-header-button {
.mat-icon {
padding-left: 10px;
font-size: 12px;
padding-top: 5px;
}
}
@media (min-width: 576px) {
::ng-deep .d-sm-block {
display: table-cell !important;
}
}
@media (max-width: 576px) {
::ng-deep .mat-paginator-page-size-label {
display: none !important;
}
}
tr.inner-detail-row {
height: 0;
.mat-column-expandedDetail {
padding: 0 10px;
}
}
.inner-element-detail {
overflow: hidden;
display: flex;
}
//@extend
tr.inner-detail-row {
td {
border-bottom: 1px solid #dee2e6;
border-top: 0px;
}
}
tr.inner-element-row.odd, tr.inner-detail-row.odd {
background-color: rgba(0, 0, 0, 0.05);
}
// tr.inner-detail-row {
// cursor: pointer !important;
// }
tr.mat-row-auth:not(.inner-expanded-row):hover {
background-color:#f8f9fa;
cursor: pointer !important;
}
tr.mat-row-auth.inner-expanded-row:hover {
cursor: pointer !important;
}
tr.inner-element-row-expanded td {
border-top: 0px;
border-bottom: 0px;
}
.additional-options-section {
padding: 0;
width: 100%;
display: inline-grid;
button {
margin: 1px 0;
color: inherit;
font-size: inherit;
border-radius: 0px;
}
}
::ng-deep .mat-menu-item {
padding: 0 10px;
}
@media (min-width: 992px) {
.d-t-lg-block {
display: table-cell !important;
}
}
th {
background: #f2f2f2;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { UnidentifiedSentencesTableComponent } from './unidentified-sentences-table.component';
describe('UnidentifiedSentencesTableComponent', () => {
let component: UnidentifiedSentencesTableComponent;
let fixture: ComponentFixture<UnidentifiedSentencesTableComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ UnidentifiedSentencesTableComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UnidentifiedSentencesTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatSort } from '@angular/material';
import { Pagination, SortField } from '@xdf/commons';
import { DynaDataSource } from '@xdf/gallery';
import { tap } from 'rxjs/operators';
import { UnidentifiedSentenceService } from './services/unidentified-sentences.service';
@Component({
selector: 'byte-unidentified-sentences-table',
templateUrl: './unidentified-sentences-table.component.html',
styleUrls: ['./unidentified-sentences-table.component.scss']
})
export class UnidentifiedSentencesTableComponent implements OnInit {
displayedColumns: string[] = ['identifier', 'count'];
dataSource: DynaDataSource;
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
@ViewChild(MatSort, { static: false }) sort: MatSort;
sortColumn: string = "count";
sortDirection: string = "desc";
statusQuickFilter: string;
pagingSize = 8;
@Input()
minDate: Date;
@Input()
maxDate: Date;
pagination: Pagination;
constructor(protected service: UnidentifiedSentenceService) { }
ngOnInit() {
this.dataSource = new DynaDataSource(this.service);
// Paginación
this.pagination = new Pagination();
this.pagination.currentPage = 0;
this.pagination.itemsPerPage = this.pagingSize;
this.pagination.filterExpression = "startDate=" + this.minDate.getTime() + "&&endDate=" + this.maxDate.getTime();
// Ordenación por defecto
if (this.sortColumn && this.sortDirection) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection;
sortField.field = this.sortColumn;
this.pagination.sortFields.push(sortField);
}
this.dataSource.load(this.pagination);
}
ngAfterViewInit(): void {
this.sort.sortChange.subscribe(() => {
console.log(this.sort);
if (!this.sort.disabled && this.sort.active) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection = this.sort.direction;
sortField.field = this.sortColumn = this.sort.active;
this.pagination.sortFields.push(sortField);
}
this.paginator.pageIndex = 0;
this.dataSource.load(this.pagination);
});
this.paginator.page
.pipe(
tap(() => {
this.pagination.currentPage = this.paginator.pageIndex;
this.pagination.itemsPerPage = this.paginator.pageSize;
this.dataSource.load(this.pagination);
})
)
.subscribe();
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomerActivityWidgetComponent } from './customer-activity-widget.component';
describe('CustomerActivityWidgetComponent', () => {
let component: CustomerActivityWidgetComponent;
let fixture: ComponentFixture<CustomerActivityWidgetComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CustomerActivityWidgetComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CustomerActivityWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { Chart } from 'angular-highcharts';
@Component({
selector: 'byte-customer-activity-widget',
templateUrl: './customer-activity-widget.component.html',
styleUrls: ['./customer-activity-widget.component.scss']
})
export class CustomerActivityWidgetComponent implements OnInit {
chart = new Chart({
chart: {
type: 'area'
},
accessibility: {
description: 'Image description: An area chart compares the nuclear stockpiles of the USA and the USSR/Russia between 1945 and 2017. The number of nuclear weapons is plotted on the Y-axis and the years on the X-axis. The chart is interactive, and the year-on-year stockpile levels can be traced for each country. The US has a stockpile of 6 nuclear weapons at the dawn of the nuclear age in 1945. This number has gradually increased to 369 by 1950 when the USSR enters the arms race with 6 weapons. At this point, the US starts to rapidly build its stockpile culminating in 32,040 warheads by 1966 compared to the USSR’s 7,089. From this peak in 1966, the US stockpile gradually decreases as the USSR’s stockpile expands. By 1978 the USSR has closed the nuclear gap at 25,393. The USSR stockpile continues to grow until it reaches a peak of 45,000 in 1986 compared to the US arsenal of 24,401. From 1986, the nuclear stockpiles of both countries start to fall. By 2000, the numbers have fallen to 10,577 and 21,000 for the US and Russia, respectively. The decreases continue until 2017 at which point the US holds 4,018 weapons compared to Russia’s 4,500.'
},
title: {
text: 'US and USSR nuclear stockpiles'
},
subtitle: {
text: 'Sources: <a href="https://thebulletin.org/2006/july/global-nuclear-stockpiles-1945-2006">' +
'thebulletin.org</a> & <a href="https://www.armscontrol.org/factsheets/Nuclearweaponswhohaswhat">' +
'armscontrol.org</a>'
},
xAxis: {
allowDecimals: false,
labels: {
},
accessibility: {
rangeDescription: 'Range: 1940 to 2017.'
}
},
yAxis: {
title: {
text: 'Nuclear weapon states'
},
labels: {
formatter: function () {
return this.value / 1000 + 'k';
}
}
},
tooltip: {
pointFormat: '{series.name} had stockpiled <b>{point.y:,.0f}</b><br/>warheads in {point.x}'
},
plotOptions: {
area: {
pointStart: 1940,
marker: {
enabled: false,
symbol: 'circle',
radius: 2,
states: {
hover: {
enabled: true
}
}
}
}
},
series: [{
name: 'USA',
data: [
null, null, null, null, null, 6, 11, 32, 110, 235,
369, 640, 1005, 1436, 2063, 3057, 4618, 6444, 9822, 15468,
20434, 24126, 27387, 29459, 31056, 31982, 32040, 31233, 29224, 27342,
26662, 26956, 27912, 28999, 28965, 27826, 25579, 25722, 24826, 24605,
24304, 23464, 23708, 24099, 24357, 24237, 24401, 24344, 23586, 22380,
21004, 17287, 14747, 13076, 12555, 12144, 11009, 10950, 10871, 10824,
10577, 10527, 10475, 10421, 10358, 10295, 10104, 9914, 9620, 9326,
5113, 5113, 4954, 4804, 4761, 4717, 4368, 4018
]
}, {
name: 'USSR/Russia',
data: [null, null, null, null, null, null, null, null, null, null,
5, 25, 50, 120, 150, 200, 426, 660, 869, 1060,
1605, 2471, 3322, 4238, 5221, 6129, 7089, 8339, 9399, 10538,
11643, 13092, 14478, 15915, 17385, 19055, 21205, 23044, 25393, 27935,
30062, 32049, 33952, 35804, 37431, 39197, 45000, 43000, 41000, 39000,
37000, 35000, 33000, 31000, 29000, 27000, 25000, 24000, 23000, 22000,
21000, 20000, 19000, 18000, 18000, 17000, 16000, 15537, 14162, 12787,
12600, 11400, 5500, 4512, 4502, 4502, 4500, 4500
]
}] as Array<any>
});
constructor() { }
ngOnInit() {
}
}
<rg-gauge-chart
[canvasWidth]="canvasWidth"
[needleValue]="value"
[options]="options"
[name]="name"
[bottomLabel]="bottomLabel" *ngIf="options"></rg-gauge-chart>
\ No newline at end of file
::ng-deep rg-gauge-chart .gauge-chart span:first-child{
font-size: 1em !important;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GaugeChartComponent } from './gauge-chart.component';
describe('GaugeChartComponent', () => {
let component: GaugeChartComponent;
let fixture: ComponentFixture<GaugeChartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GaugeChartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GaugeChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { DecimalPipe } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
export class GaugeChart {
hasNeedle?: boolean;
needleColor?: string;
needleUpdateSpeed?: number;
arcColors?: Array<string>;
arcDelimiters?: Array<number>;
rangeLabel?: Array<string>;
needleStartValue?: number;
}
export const DEFAULT_OPTIONS_GAUGE_CHART: GaugeChart = {
hasNeedle: true,
needleColor: '#676a6c',
needleUpdateSpeed: 1000,
arcColors: ['#1ab394', '#f8ac59', '#ed5565'],
arcDelimiters: [16, 33],
rangeLabel: ['0', '6'],
needleStartValue: 1,
}
@Component({
selector: 'byte-gauge-chart',
templateUrl: './gauge-chart.component.html',
styleUrls: ['./gauge-chart.component.scss']
})
export class GaugeChartComponent implements OnInit {
@Input()
name: string = "Gauge chart";
@Input()
value: any = 1.2;
@Input()
unit: string = "";
@Input()
options: GaugeChart;
@Input()
format: Boolean = false;
@Input()
maxValue: number = 6;
canvasWidth = 250
centralLabel = '25'
bottomLabel = '';
constructor() { }
ngOnInit() {
let valueNumber = +this.value;
this.value = this.value / this.maxValue * 100;
console.log(this.value);
if (this.format) {
valueNumber = valueNumber / 1000;
}
let valueFormater:number = 0;
if (valueNumber >= 86400) {
this.unit = "d";
valueFormater = valueNumber / 86400;
} else if (valueNumber >= 3600) {
this.unit = "h";
valueFormater = valueNumber / 3600;
} else if (valueNumber >= 60){
this.unit = "m";
valueFormater = valueNumber / 60;
} else {
this.unit = "s";
valueFormater = valueNumber;
}
this.bottomLabel = valueFormater.toFixed(1) + ' ' + this.unit;
if (!this.options) {
this.options = DEFAULT_OPTIONS_GAUGE_CHART;
}
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HeatMapComponent } from './heat-map.component';
describe('HeatMapComponent', () => {
let component: HeatMapComponent;
let fixture: ComponentFixture<HeatMapComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeatMapComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HeatMapComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
//import { ToogleService } from '@xdf/layouts';
import { Chart } from 'angular-highcharts';
import * as Highcharts from 'highcharts';
import { Subscription } from 'rxjs';
@Component({
selector: 'byte-heat-map',
templateUrl: './heat-map.component.html',
styleUrls: ['./heat-map.component.scss']
})
export class HeatMapComponent implements OnInit, OnDestroy {
@Input()
data: Array<any> = [];
@Input()
minDate: Date;
@Input()
maxDate: Date;
subscriptionToogle: Subscription;
constructor(
//protected toogleService: ToogleService
) {
}
ngOnDestroy(): void {
if (this.subscriptionToogle) {
this.subscriptionToogle.unsubscribe();
}
}
chart: Chart;
ngOnInit() {
if (!this.maxDate || !this.minDate) {
return;
}
this.chart = new Chart({
chart: {
marginTop: 50,
marginBottom: 100,
marginLeft: 150,
marginRight: 40,
type: 'heatmap',
events: {
load: function (event: any) {
event.target.reflow();
}
}
},
boost: {
useGPUTranslations: true
},
credits: {
enabled: false
},
title: {
text: '',
align: 'center',
x: 40
},
exporting: {
enabled: false
},
legend: {
enabled: true
},
xAxis: {
type: 'datetime',
min: this.minDate.getTime(),
max: this.maxDate.getTime(),
labels: {
align: 'left',
x: 5,
y: 14,
format: '{value:%d/%m}' // long month
},
showLastLabel: false,
tickLength: 10,
tickWidth: 1
},
yAxis: {
title: {
text: null
},
labels: {
formatter: function () {
//return 'H' + (this.value + 1) + ': ' + (this.value < 10 ? '0' : '') + this.value + ':00' ;
return 'H' + (this.value + 1) + ': ' + (3 * this.value < 10 ? '0' : '') + 3 * this.value + ':00'
+ ' - ' + ((3 * this.value + 3) < 10 ? '0' : '') + (3 * this.value + 3) + ':00';
},
format: 'H{value:}:{3*value}:00 - {value + 3}:00'
},
minPadding: 0,
maxPadding: 0,
startOnTick: false,
endOnTick: false,
tickPositions: [0, 1, 2, 3, 4, 5, 6, 7],
tickWidth: 1,
min: 0,
max: 7,
reversed: true
},
colorAxis: {
stops: [
[0, '#dceefa'],
[0.5, '#85c5ed'],
[1, '#1c84c6']
],
min: 0,
max: 25,
startOnTick: false,
endOnTick: false,
labels: {
format: '{value}'
}
},
series: [{
boostThreshold: 100,
borderWidth: 4,
nullColor: '#f3f3f4',
borderColor: '#f3f3f4',
colsize: 24 * 36e5, // one day
tooltip: {
headerFormat: '{point.x:%e %b}',
//pointFormat: '{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>'
pointFormat: '<b>{point.value} mensajes</b>'
},
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
data: this.data
}] as Array<any>
});
// this.subscriptionToogle = this.toogleService.changeEmitted$.subscribe(change => {
// setTimeout(() => {
// Highcharts.charts.forEach(chart => chart.reflow());;
// console.log("test");
// }, 500);
// });
}
}
<div class="ccard h-100 d-flex flex-column px-2 py-3">
<div class="d-flex text-center">
<div class="flex-grow-1 mb-3">
<div class="text-nowrap lh text-100 text-dark-l2">
{{ header | translate }}&nbsp;<i class="fa fa-info-circle" matTooltip="{{ 'dashboards.operative.session.time.desc' | translate }}"></i>
</div>
<div class="lh">
<span class="text-170 text-secondary-d4" style="font-size: 1.3em !important;">
{{ value?.value | millisToDayHourMinuteSecond }}
</span>
</div>
</div>
<!-- <div class="session-icon">
<i class="fa fa-2x fa-clock-o" matTooltip="{{ 'action.grid.refresh' | translate }}"></i>
</div> -->
</div>
<div class="align-self-center">
<xdf-peity #peti [values]="value?.history" [type]="'line'" theme="primary" [attributes]='options'></xdf-peity>
</div>
</div>
\ No newline at end of file
.ccard {
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,.075);
border-radius: .5rem;
border: 1px solid #e0e5e8;
background-color: #fff;
position: relative;
}
.text-dark-l2 {
color: #60626a!important;
}
.text-100 {
font-size: 1em!important;
}
.text-secondary-d4 {
color: #4c5b70!important;
}
.text-170 {
font-size: 1.7em!important;
}
.text-blue {
color: #1279cd!important;
}
.text-nowrap {
white-space: nowrap!important;
}
.lh {
line-height: 1.5 !important;
}
.session-icon {
position: relative;
top: 0px;
right: 12px;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InactivitySesionComponent } from './inactivity-sesion.component';
describe('InactivitySesionComponent', () => {
let component: InactivitySesionComponent;
let fixture: ComponentFixture<InactivitySesionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InactivitySesionComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InactivitySesionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { AfterViewInit, Component, HostListener, Input, OnInit, ViewChild } from '@angular/core';
import { PeityComponent } from '@xdf/graph';
import { PetyWidgetModel } from '../summary-peti-widget/summary-peti-widget.component';
@Component({
selector: 'byte-inactivity-sesion',
templateUrl: './inactivity-sesion.component.html',
styleUrls: ['./inactivity-sesion.component.scss']
})
export class InactivitySesionComponent implements OnInit, AfterViewInit {
@Input() options = {
width: 200,
height: 60,
fill: "#dfecf9",
stroke: "#024990",
strokeWidth: 1
};
@Input() values = [1, 12, 3, 15, 5, -1, 10];
@Input() header;
@Input() value: PetyWidgetModel;
@ViewChild('peti', { static: false })
peti: PeityComponent;
constructor() { }
ngOnInit() {
}
ngAfterViewInit() {
if (this.peti) {
const width = window.innerWidth;
this.updateWidth(width);
this.peti.updateSize();
}
}
@HostListener('window:resize',['$event'])
public onResize(event) {
const width = event.target.innerWidth;
this.updateWidth(width);
this.peti.updateSize();
}
private updateWidth(width: number) {
if (width >= 1600) {
this.options.width = 300
} else if (width >= 1350) {
this.options.width = 200
} else if (width >= 768){
this.options.width = 150;
} else if (width >= 576){
this.options.width = 200;
} else {
this.options.width = 300;
}
}
}
<div class="row">
<div class="col-12">
<div class="mail-box-content consult pb-3" style="margin: -15px -15px 15px -15px;">
<div class="expansion-content pull-right">
<mat-form-field>
<mat-label>{{'query.filters.startDate' | translate }}</mat-label>
<input matInput [ngxMatDatetimePicker]="startPicker" [(ngModel)]="startDateTime" id="startPickerCIP" (click)="startPicker.open()"
onkeyup="this.value=this.value.replace(/.*/gi, '');">
<mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #startPicker></ngx-mat-datetime-picker>
</mat-form-field>
<mat-form-field style="margin-left: 20px;">
<mat-label>{{'query.filters.endDate' | translate }}</mat-label>
<input matInput [ngxMatDatetimePicker]="endPicker" [(ngModel)]="endDateTime" (click)="endPicker.open()" onkeyup="this.value=this.value.replace(/.*/gi, '');">
<mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #endPicker></ngx-mat-datetime-picker>
</mat-form-field>
<!-- <mat-form-field style="margin-left: 20px;">
<mat-label>{{'query.filters.channel' | translate }}</mat-label>
<mat-select [(ngModel)]="selectedChannel" name="food">
<mat-option *ngFor="let channel of channels" [value]="channel.value">
{{ channel.label | translate }}
</mat-option>
</mat-select>
</mat-form-field> -->
<button class="btn btn-primary btn-sm ng-star-inserted" style="margin-left: 15px;
margin-top: 0px !important;" type="button" [disabled]="!startDateTime || !endDateTime"
(click)="search()">
<i class="fa fa-search"></i><span class="btn-label">Buscar</span>
</button>
</div>
<div class="pull-left icon" *ngIf="icon">
<div [innerHTML]="icon"></div>
</div>
<h3 class="grid-title">
<span>{{ header | translate }}</span>
</h3>
<span *ngIf="headerDesc" class="small">{{ headerDesc | translate }}</span>
</div>
</div>
</div>
\ No newline at end of file
.ibox-content {
font-size: 12px;
}
.table.invoice-total {
margin-bottom: 0px;
}
.grid-title {
margin-top: 2px !important;
}
.mat-form-field + .mat-form-field {
margin-left: 8px;
}
.expansion-content {
padding-top: 5px;
}
.mail-box-content.consult {
padding: 10px 20px;
}
.mail-box-content {
h3 {
margin: 0;
font-weight: 500;
}
}
.icon {
padding: 5px 10px 0 0;
}
.debit {
color: red;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { OperativeDashboardFilterComponent } from './operative-dashboard-filter.component';
describe('OperativeDashboardFilterComponent', () => {
let component: OperativeDashboardFilterComponent;
let fixture: ComponentFixture<OperativeDashboardFilterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ OperativeDashboardFilterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OperativeDashboardFilterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { NgxMatDatetimePicker } from '@angular-material-components/datetime-picker';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'byte-operative-dashboard-filter',
templateUrl: './operative-dashboard-filter.component.html',
styleUrls: ['./operative-dashboard-filter.component.scss']
})
export class OperativeDashboardFilterComponent implements OnInit {
form: FormGroup;
channels = [
{
value: "W",
label: "Whatsapp"
},
{
value: "F",
label: "Facebook"
}
];
endDateTime: Date;
startDateTime: Date;
selectedChannel: string;
isLoading: Boolean = false;
@Input()
header: string;
@Input()
headerDesc: string;
@Input()
icon: string = '<i class="fa fa-bar-chart-o"></i>';
@Output()
onFilter: EventEmitter<any> = new EventEmitter();
constructor(
protected route: ActivatedRoute,
protected router: Router) {
this.selectedChannel = this.channels[0].value;
}
ngOnInit() {
const title = this.route.snapshot.data['title'];
const titleDesc = this.route.snapshot.data['titleDesc'];
this.header = title;
this.headerDesc = titleDesc;
this.form = new FormGroup({
id: new FormControl('', Validators.required),
period: new FormControl('', Validators.required)
});
this.endDateTime = new Date();
this.startDateTime = this.getStartDate(this.endDateTime);
this.search();
}
search() {
const filters = {startDateTime: this.startDateTime, endDateTime: this.endDateTime, channel: this.selectedChannel}
this.onFilter.emit(filters);
}
private getStartDate(endDate: Date): Date {
return new Date(Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), 5, 0, 0));
}
keyup(event) {
event.preventDefault();
}
clickEvent (event: NgxMatDatetimePicker<any>) {
event.open();
}
}
<byte-operative-dashboard-filter (onFilter)="filter($event)"></byte-operative-dashboard-filter>
<ng-container *ngIf="isLoading">
<div class="spiner-example">
<div class="sk-spinner sk-spinner-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</div>
</ng-container>
<ng-container *ngIf="!isLoading && isDataLoaded">
<div class="row">
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-inactivity-sesion header="dashboards.operative.inactivity.sessions" [value]="data?.summary?.sessionInactivity"
[options]="optionsInactivitySesion"></byte-inactivity-sesion>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="dashboards.operative.sessions" [value]="data?.summary?.totalSessions"
[options]="totalSesionsOptions" tooltip="dashboards.operative.session.total.desc">
</byte-summary-peti-widget>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="dashboards.operative.receveid"
[value]="data?.summary?.totalReceivedMessages" [options]="totalReceivedOptions"
tooltip="dashboards.operative.message.received.desc" format="true" [formatter]="formatterNumber">
</byte-summary-peti-widget>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="dashboards.operative.sended" [value]="data?.summary?.totalSentMessages"
[options]="totalSentOptions" tooltip="dashboards.operative.message.sended.desc" format="true"
[formatter]="formatterNumber"></byte-summary-peti-widget>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-lg-8">
<div class="ibox" style="padding-top: 15px;">
<div class="ibox-content b-s" style="text-align: center;">
<byte-gauge-chart [value]="data?.averages?.firstResponseAverage" unit="s"
name="{{ 'Tiempo promedio de primera respuesta' | translate }}" style="display: inline-block;">
</byte-gauge-chart>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-lg-4">
<div class="ibox" style="padding-top: 15px;">
<div class="ibox-content b-s" style="text-align: center;">
<!-- <byte-gauge-chart [value]="data?.averages?.sessionAverage" name="{{ 'dashboards.operative.avg.sessions' | translate }}" style="display: inline-block;">
</byte-gauge-chart> -->
<div class="widget style1">
<div class="row">
<div class="col-12 text-center">
<i class="fa fa-user fa-5x"></i>
</div>
<div class="col-12 text-center">
<span> {{ 'dashboards.operative.avg.sessions' | translate }} </span>
<h2 class="font-bold">{{ data?.averages?.sessionAverage | number : '1.1-1'}}</h2>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" *ngIf="startDate && endDate">
<div class="col-12">
<xdf-widget-panel [options]="panelOptions">
<div class="row">
<div class="col-12">
<byte-heat-map [data]="data?.customerMessageDetail" [minDate]="startDate" [maxDate]="endDate">
</byte-heat-map>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
</ng-container>
\ No newline at end of file
/* PROGRESS CIRCLE COMPONENT */
.circliful {
margin:auto;
position: relative;
}
.circle-text, .circle-info, .circle-text-half, .circle-info-half {
width: 100%;
position: absolute;
text-align: center;
display: inline-block;
}
.circle-info, .circle-info-half {
color: #999;
}
.circliful .fa {
margin: -10px 3px 0 3px;
position: relative;
bottom: 4px;
}
.ibox {
margin-bottom: 15px !important;
}
.b-s {
box-shadow: 0 0 13px 0 rgba(62, 44, 90, 0.08);
// border: 1px solid rgba(0, 0, 0, 0.09);
}
:host.row.wrapper.border-bottom.white-bg.page-heading {
display: none !important;
}
.dashboard-panel {
font-family: Roboto,"Helvetica Neue",Helvetica,Arial;
font-size: .7125rem;
margin: -10px -15px;
}
.uppanel {
padding: 15px 20px 5px;
margin-bottom: 15px;
}
.headerpanel {
padding: 10px 30px;
background-color: white;
margin-left: -15px;
margin-right: -15px;
margin-bottom: 15px;
}
.mypanel {
margin: 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
position: relative;
background-color: #fff;
-webkit-box-shadow: 0 0 13px 0 rgba(62,44,90,.08);
box-shadow: 0 0 13px 0 rgba(62,44,90,.08);
margin-bottom: 1.5rem;
border-radius: 4px;
border: 1px solid rgba(0,0,0,.09);
border-bottom: 1px solid #e0e0e0;
border-radius: 4px;
-webkit-transition: border .5s ease-out;
transition: border .5s ease-out;
}
.header-date {
padding-bottom: 20px;
}
.subheader {
padding-top: 20px;
margin-bottom: 20px;
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.subheader-icon {
color: #a8a6ac;
margin-right: .25rem;
}
.subheader-title {
font-size: 1.375rem;
font-weight: 500;
color: #505050;
text-shadow: #fff 0 1px;
margin: 0;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.stat-list {
padding: 10px 15px 20px;
}
::ng-deep xdf-widget-panel .panel-toolbar button {
display: none !important;
}
::ng-deep.navbar-header {
margin-bottom: 10px;
}
@media (max-width: 1180px) {
.text-100 {
font-size: 11px !important;
}
}
@media (max-width: 990px) {
.text-100 {
font-size: 1em !important;
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { OperativeDashboardComponent } from './operative-dashboard.component';
describe('OperativeDashboardComponent', () => {
let component: OperativeDashboardComponent;
let fixture: ComponentFixture<OperativeDashboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ OperativeDashboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OperativeDashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { NotificationService, NotificationType } from '@xdf/commons';
import { SharedFilterService } from 'projects/bytebot-html/src/app/services/shared-filters.service';
import { OperationalConsultationService } from '../../../../services/operational-consultation.service';
import { PetiFormatter } from '../summary-peti-widget/formatter/formatter';
import { MessageCountFormatter } from '../summary-peti-widget/formatter/message-count.formatter';
import { PetyWidgetModel } from '../summary-peti-widget/summary-peti-widget.component';
@Component({
selector: 'byte-operative-dashboard',
templateUrl: './operative-dashboard.component.html',
styleUrls: ['./operative-dashboard.component.scss']
})
export class OperativeDashboardComponent implements OnInit {
latestDate = new Date();
existDateRange: Boolean = false;
isLoading: Boolean = false;
startDate: Date;
endDate: Date;
data: any;
isDataLoaded: Boolean = false;
formatterNumber: PetiFormatter = new MessageCountFormatter();
panelOptions = {
elevation: false,
title: {
primary: 'dashboards.operative.message.customer.activity',
secondary: ''
}
}
optionsInactivitySesion = {
width: 300,
height: 60,
fill: "#C8FEFF",
stroke: "#23c6c8",
strokeWidth: 1
};
totalSesionsOptions = {
width: 300,
height: 60,
fill: "#CFECFF",
stroke: "#1c84c6",
strokeWidth: 1
};
totalReceivedOptions = {
width: 300,
height: 60,
fill: "#D2EEE9",
stroke: "#1ab394",
strokeWidth: 1
};
totalSentOptions = {
width: 300,
height: 60,
fill: "#FFD9DD",
stroke: "#ed5565",
strokeWidth: 1
};
constructor(
protected operationalConsultationService: OperationalConsultationService,
protected notificationService: NotificationService) { }
ngOnInit() {
}
filter(filters) {
this.isDataLoaded = false;
this.isLoading = true;
this.startDate = filters['startDateTime'];
this.endDate = filters['endDateTime'];
const channel = filters['channel'];
this.operationalConsultationService.getSummary(this.startDate, this.endDate, channel).subscribe(response => {
this.data = response;
this.isLoading = false;
this.isDataLoaded = true;
}, err => {
this.isLoading = false;
});
}
}
export interface PetiFormatter {
format(value: number): string;
}
\ No newline at end of file
import { PetiFormatter } from './formatter';
export class MessageCountFormatter implements PetiFormatter {
format(value: number): string {
if (value < 1000) {
return "" + value;
}
let newNumber: number = value/1000;
return newNumber.toFixed(1) + "K";
}
}
\ No newline at end of file
<div class="ccard h-100 d-flex flex-column px-2 py-3">
<div class="d-flex text-center">
<div class="flex-grow-1 mb-3">
<div class="text-nowrap lh text-100 text-dark-l2">
{{ header | translate }}&nbsp;<i class="fa fa-info-circle" matTooltip="{{ tooltip | translate }}"></i>
</div>
<div class="lh">
<span class="text-170 text-secondary-d4">
{{ formatterValue }}
</span>
<!-- <span class="text-blue text-nowrap ml-n1">
&nbsp;+{{value?.percent}}%
<i *ngIf="value?.up" class="fa fa-caret-up"></i>
<i *ngIf="value?.down" class="fa fa-caret-down"></i>
</span> -->
</div>
</div>
</div>
<div class="align-self-center">
<xdf-peity #peti [values]="value?.history" [type]="'line'" theme="primary" [attributes]='options'></xdf-peity>
</div>
</div>
.ccard {
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,.075);
border-radius: .5rem;
border: 1px solid #e0e5e8;
background-color: #fff;
position: relative;
}
.text-dark-l2 {
color: #60626a!important;
}
.text-100 {
font-size: 1em;
}
.text-secondary-d4 {
color: #4c5b70!important;
}
.text-170 {
font-size: 1.7em;
}
.text-blue {
color: #1279cd!important;
}
.text-nowrap {
white-space: nowrap!important;
}
.lh {
line-height: 1.5 !important;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SummaryPetiWidgetComponent } from './summary-peti-widget.component';
describe('SummaryPetiWidgetComponent', () => {
let component: SummaryPetiWidgetComponent;
let fixture: ComponentFixture<SummaryPetiWidgetComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SummaryPetiWidgetComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SummaryPetiWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, HostListener, Input, OnInit, ViewChild } from '@angular/core';
import { PeityComponent } from '@xdf/graph';
import { PetiFormatter } from './formatter/formatter';
declare var jQuery: any;
export class PetyWidgetModel {
percent: number;
up?: boolean;
down?: boolean;
value: number;
history: Array<number>;
}
@Component({
selector: 'byte-summary-peti-widget',
templateUrl: './summary-peti-widget.component.html',
styleUrls: ['./summary-peti-widget.component.scss']
})
export class SummaryPetiWidgetComponent implements OnInit {
@Input() options = {
width: 200,
height: 60,
fill: "#dfecf9",
stroke: "#024990",
strokeWidth: 1
};
@Input() values = [1, 12, 3, 15, 5, -1, 10];
@Input() header;
@Input() value: PetyWidgetModel;
@Input() tooltip: string;
@Input() formatter: PetiFormatter
@ViewChild('peti', { static: false })
peti: PeityComponent;
constructor() { }
ngOnInit() {
}
ngAfterViewInit() {
if (this.peti) {
const width = window.innerWidth;
this.updateWidth(width);
this.peti.updateSize();
}
}
get formatterValue(): string|number {
if (this.formatter) {
return this.formatter.format(this.value.value);
}
return this.value.value;
}
@HostListener('window:resize',['$event'])
public onResize(event) {
console.log("aea");
const width = event.target.innerWidth;
this.updateWidth(width);
this.peti.updateSize();
}
private updateWidth(width: number) {
if (width >= 1600) {
this.options.width = 300
} else if (width >= 1350) {
this.options.width = 200
} else if (width >= 768){
this.options.width = 150;
} else if (width >= 576){
this.options.width = 200;
} else {
this.options.width = 300;
}
}
}
<div class="card amount-card o-hidden"><iframe class="chartjs-hidden-iframe" tabindex="-1" style="display: block; overflow: hidden; border: 0px; margin: 0px; top: 0px; left: 0px; bottom: 0px; right: 0px; height: 100%; width: 100%; position: absolute; pointer-events: none; z-index: -1;"></iframe>
<div class="card-block">
<h2 class="f-w-400">$23,567</h2>
<p class="text-muted f-w-600 f-16"><span class="text-c-blue">Amount</span> processed</p>
</div>
<canvas id="amount-processed" height="100" width="475" style="display: block;"></canvas>
</div>
\ No newline at end of file
.amount-card {
overflow: hidden;
}
.o-hidden {
overflow: hidden;
}
.card {
border-radius: 5px;
-webkit-box-shadow: 0 1px 11px 0 rgba(0, 0, 0, 0.12);
box-shadow: 0 1px 11px 0 rgba(0, 0, 0, 0.12);
border: none;
margin-bottom: 30px;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WidgetPetiComponent } from './widget-peti.component';
describe('WidgetPetiComponent', () => {
let component: WidgetPetiComponent;
let fixture: ComponentFixture<WidgetPetiComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WidgetPetiComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WidgetPetiComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'byte-widget-peti',
templateUrl: './widget-peti.component.html',
styleUrls: ['./widget-peti.component.scss']
})
export class WidgetPetiComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedFilterService {
private _startDate:Date;
private _endDate:Date;
get startDate():Date {
return this._startDate;
}
get endDate():Date {
return this._endDate;
}
setStartDate(date: Date) {
this._startDate = date;
}
setEndDate(date: Date) {
this._endDate = date;
}
}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
[ [
{ {
"id": 30, "id": 38,
"name": "security", "name": "BBOT_DASHBOARD",
"label": "menu.security", "label": "menu.parent.dashboard",
"icon": "<i class=\"fa fa-address-book\"></i>", "icon": "<i class=\"fa fa-area-chart\"></i>",
"fullPath": "/security", "fullPath": "/dashboards",
"singlePath": "security", "singlePath": "dashboards",
"isProgram": false, "isProgram": false,
"children": [ "children": [
{ {
"id": 32, "id": 32,
"name": "user", "name": "operative_dashboard",
"label": "menu.security.user", "label": "menu.dashboard.operative",
"icon": null, "icon": null,
"fullPath": "/security/user", "fullPath": "/dashboards/operative",
"singlePath": "user", "singlePath": "operative",
"isProgram": true, "isProgram": true,
"children": [] "children": []
}, },
{ {
"id": 32, "id": 32,
"name": "user_role", "name": "customer_interaction_dashboard",
"label": "menu.security.user.role", "label": "menu.dashboard.customer.interaction",
"icon": null, "icon": null,
"fullPath": "/security/user-role", "fullPath": "/dashboards/customer-interaction",
"singlePath": "user-role", "singlePath": "customer-interaction",
"isProgram": true, "isProgram": true,
"children": [] "children": []
} }
] ]
},
{
"id": 37,
"name": "CONVERSATIONAL_AGENT",
"label": "menu.parent.agent",
"icon": "<i class=\"fa fa-cog\"></i>",
"fullPath": "/configuration/agent",
"singlePath": "configuration",
"isProgram": true,
"children": []
} }
] ]
\ No newline at end of file
{
"summary": {
"sessionInactivity": {
"value": 30,
"history": [
10,
10,
10,
10,
10,
10,
10
],
"percent": 2,
"up": true
},
"totalSessions": {
"value": 30,
"percent": 2,
"up": true,
"history": [10, 8, 6, 5, 6, 8, 10]
},
"totalReceivedMessages": {
"value": 30,
"percent": 2,
"down": true,
"history": [1, 5, 3, 8, 4, 10, 0]
},
"totalSentMessages": {
"value": 30,
"percent": 2,
"down": true,
"history": [10, 8, 6, 5, 6, 8, 10]
}
},
"averages": {
"firstResponseAverage": 1.2,
"sessionAverage": 1.2
},
"customerMessageDetail": [
[
1577854800000,
0,
2.7
],
[
1577854800000,
1,
2.5
],
[
1577854800000,
2,
3.5
],
[
1577854800000,
3,
4.5
],
[
1577854800000,
4,
5.5
],
[
1577854800000,
5,
2.5
],
[
1577854800000,
6,
2.5
],
[
1577854800000,
7,
2.5
],
[
1577854800000,
8,
2.5
],
[
1577854800000,
9,
2.5
],
[
1577854800000,
10,
2.5
],
[
1577854800000,
11,
2.5
],
[
1577854800000,
12,
2.5
],
[
1577854800000,
13,
2.5
],
[
1577854800000,
14,
2.5
],
[
1577854800000,
15,
2.5
],
[
1577854800000,
16,
2.5
],
[
1577854800000,
17,
2.5
],
[
1577854800000,
18,
2.5
],
[
1577854800000,
19,
2.5
],
[
1577854800000,
20,
2.5
],
[
1577854800000,
21,
2.5
],
[
1577854800000,
22,
2.5
],
[
1577854800000,
23,
2.5
],
[
1577941200000,
0,
9
],
[
1577941200000,
1,
11
],
[
1577941200000,
2,
12
],
[
1577941200000,
3,
11
],
[
1577941200000,
4,
15
],
[
1577941200000,
5,
16
],
[
1577941200000,
6,
17
],
[
1577941200000,
7,
18
],
[
1577941200000,
8,
19
],
[
1577941200000,
9,
11
],
[
1577941200000,
10,
12
],
[
1577941200000,
11,
13
],
[
1577941200000,
12,
14
],
[
1577941200000,
13,
12
],
[
1577941200000,
14,
15
],
[
1577941200000,
15,
19
],
[
1577941200000,
16,
20
],
[
1577941200000,
17,
16
],
[
1577941200000,
18,
18
],
[
1577941200000,
19,
15
],
[
1577941200000,
20,
15
],
[
1577941200000,
21,
15
],
[
1577941200000,
22,
15
],
[
1577941200000,
23,
15
],
[
1578027600000,
0,
13
],
[
1578027600000,
1,
12
],
[
1578027600000,
2,
3
],
[
1578027600000,
3,
12
],
[
1578027600000,
4,
4
],
[
1578027600000,
5,
8
],
[
1578027600000,
6,
15
],
[
1578027600000,
7,
12
],
[
1578027600000,
8,
19
],
[
1578027600000,
9,
2
],
[
1578027600000,
10,
16
],
[
1578027600000,
11,
16
],
[
1578027600000,
12,
19
],
[
1578027600000,
13,
9
],
[
1578027600000,
14,
7
],
[
1578027600000,
15,
19
],
[
1578027600000,
16,
9
],
[
1578027600000,
17,
13
],
[
1578027600000,
18,
17
],
[
1578027600000,
19,
13
],
[
1578027600000,
20,
16
],
[
1578027600000,
21,
10
],
[
1578027600000,
22,
10
],
[
1578027600000,
23,
19
],
[
1578114000000,
0,
17
],
[
1578114000000,
1,
6
],
[
1578114000000,
2,
13
],
[
1578114000000,
3,
4
],
[
1578114000000,
4,
7
],
[
1578114000000,
5,
4
],
[
1578114000000,
6,
9
],
[
1578114000000,
7,
9
],
[
1578114000000,
8,
3
],
[
1578114000000,
9,
9
],
[
1578114000000,
10,
11
],
[
1578114000000,
11,
7
],
[
1578114000000,
12,
14
],
[
1578114000000,
13,
15
],
[
1578114000000,
14,
8
],
[
1578114000000,
15,
11
],
[
1578114000000,
16,
14
],
[
1578114000000,
17,
19
],
[
1578114000000,
18,
3
],
[
1578114000000,
19,
20
],
[
1578114000000,
20,
6
],
[
1578114000000,
21,
13
],
[
1578114000000,
22,
10
],
[
1578114000000,
23,
18
],
[
1578114000000,
24,
16
]
]
}
\ No newline at end of file
...@@ -7,5 +7,7 @@ ...@@ -7,5 +7,7 @@
"estados", "estados",
"motivos", "motivos",
"categoria_motivo", "categoria_motivo",
"CONVERSATIONAL_AGENT" "CONVERSATIONAL_AGENT",
"OPERATIVE_DASHBOARD",
"customer_interaction_dashboard"
] ]
\ No newline at end of file
...@@ -71,5 +71,31 @@ ...@@ -71,5 +71,31 @@
"label.file-upload.error.description": "Ha ocurrido un error en la validación del archivo.", "label.file-upload.error.description": "Ha ocurrido un error en la validación del archivo.",
"btn.retry": "Cargar otro archivo", "btn.retry": "Cargar otro archivo",
"btn.synchronize": "Publicar", "btn.synchronize": "Publicar",
"btn.upload.file": "Subir archivo" "btn.upload.file": "Subir archivo",
"query.filters.endDate": "Fecha de fin",
"query.filters.startDate": "Fecha de inicio",
"query.filters.channel": "Canal",
"dashboards.operative.title": "Dashboard de indicadores operativos",
"dashboards.operative.title.desc": "Consulte los indicadores de sesión y mensajes dentro de las operaciones del bot",
"dashboards.customer.interaction.title": "Dashboard de indicadores de interacción",
"dashboards.customer.interaction.title.desc": "Consulte los indicadores de interacción en relación a las intenciones del cliente",
"dashboards.operative.session.time.desc": "Tiempo en días, horas y minutos transcurridos desde la última sesión",
"dashboards.operative.session.total.desc": "Número de conversaciones realizadas a través del bot",
"dashboards.operative.message.received.desc": "Número de mensajes generados por los clientes a través del bot",
"dashboards.operative.message.sended.desc": "Número de mensajes generados en respuesta a la solicitud de un cliente",
"dashboards.operative.message.customer.activity": "Frecuencia de mensajes por hora",
"pipe.format.day": "d",
"pipe.format.hour": "h",
"pipe.format.minute": "m",
"pipe.format.seconds": "s",
"dashboards.operative.inactivity.sessions": "Tiempo de inactividad",
"dashboards.operative.avg.sessions": "Promedio de sesiones por cliente",
"dashboards.operative.sessions": "Total de sesiones",
"dashboards.operative.receveid": "Total de mensajes recibidos",
"dashboards.operative.sended": "Total de mensajes enviados",
"dashboards.customer.interaction.tracing.intent": "Histórico de las intenciones",
"dashboards.customer.interaction.avg.intent": "Promedio de intenciones por cliente",
"dashboards.customer.interaction.goals": "Cantidad de objetivos cumplidos",
"dashboards.customer.interaction.sentences": "Frases no identificadas",
"dashboards.customer.interaction.intents": "Intenciones más utilizadas"
} }
\ No newline at end of file
...@@ -11,5 +11,6 @@ ...@@ -11,5 +11,6 @@
"menu.settings.system": "System settings", "menu.settings.system": "System settings",
"menu.settings.valpos": "Possible values", "menu.settings.valpos": "Possible values",
"menu.business.config": "Settings", "menu.business.config": "Settings",
"menu.ticket.manager": "Ticket manager" "menu.ticket.manager": "Ticket manager",
"menu.parent.dashboard": "Dashboard"
} }
\ No newline at end of file
...@@ -12,5 +12,8 @@ ...@@ -12,5 +12,8 @@
"menu.settings.valpos": "Valores posibles", "menu.settings.valpos": "Valores posibles",
"menu.business.config": "Configuración", "menu.business.config": "Configuración",
"menu.ticket.manager": "Gestor de tickets", "menu.ticket.manager": "Gestor de tickets",
"menu.parent.agent": "Agentes" "menu.parent.agent": "Agentes",
"menu.parent.dashboard": "Dashboards",
"menu.dashboard.operative": "Indicadores operativos",
"menu.dashboard.customer.interaction": "Indicadores de interacción"
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment