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 @@
}
},
"@xdf/gallery": {
"version": "file:../../XDF/ng-byte-framework/dist/xdf-gallery/xdf-gallery-1.0.8.tgz",
"integrity": "sha512-eOUgs9LGPAVJh/pPBwbNfv+3ZlIlmm7YgO01lFzZUD9bzETOxIfj3DpOluRE4WZxL2UBLB+c3uYjczwoEnEPlg==",
"version": "file:../../XDF/ng-byte-framework/dist/xdf-gallery/xdf-gallery-1.0.15.tgz",
"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": {
"tslib": "^1.9.0"
}
},
"@xdf/layouts": {
"version": "file:../../XDF/ng-byte-framework/dist/xdf-layouts/xdf-layouts-1.0.1.tgz",
"integrity": "sha512-yF8ay/2Yc2/o3AjBIcNkMIY0PmKD/OEfu1GHVymfVUWp3F5BrTXYo3Lvy1Nz/E+GEN9aDtGEkyrZe0zpzO+Irg==",
"version": "1.0.3",
"resolved": "http://192.168.27.7:4873/@xdf%2flayouts/-/layouts-1.0.3.tgz",
"integrity": "sha512-bLcyoQpl3Xk7QSz8jnX/G4RdhuUfOI3+htwZ5iag0dly/tjY3+LclDNMNIRnYo0QVRTYBQQiClOTG2QT0/hWJg==",
"requires": {
"tslib": "^1.9.0"
}
......@@ -2022,6 +2030,15 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"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": {
"version": "9.0.11",
"resolved": "https://registry.npmjs.org/angular-highcharts/-/angular-highcharts-9.0.11.tgz",
......@@ -4459,6 +4476,15 @@
"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": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
......@@ -6488,6 +6514,15 @@
"resolved": "http://192.168.27.7:4873/function-bind/-/function-bind-1.1.1.tgz",
"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": {
"version": "5.0.0",
"resolved": "http://192.168.27.7:4873/genfun/-/genfun-5.0.0.tgz",
......
import { NgModule } from '@angular/core';
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 { BytebotLayoutComponent } from './modules/bytebot-layout/bytebot-layout/bytebot-layout.component';
import { HomeComponent } from './views/home/home.component';
const routes: Routes = [
// Remover para SSO
{ path: 'login', component: LoginComponent },
// Main redirect
{ path: '', redirectTo: 'home', pathMatch: 'full', canActivate: [AuthGuard] },
{ path: '', redirectTo: 'home', pathMatch: 'full'},
{
path: '', component: CustomLayoutComponent,
children: [
{ 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: 'settings', data: { breadcrumb: 'Ajustes Generales' }, canLoad: [AuthGuard],
loadChildren: () => import('./modules/settings/settings.module').then(m => m.SettingsModule)
},
{ path: 'home', component: HomeComponent, data: { breadcrumb: 'Home' } }
]
},
{ path: '', component: BaseLayoutComponent,
children: [
{
path: 'configuration', data: { breadcrumb: 'Agentes' }, canLoad: [AuthGuard],
loadChildren: () => import('./modules/agent/agent.module').then(m => m.AgentModule)
path: 'dashboards', data: { breadcrumb: 'Dashboards' },
loadChildren: () => import('./modules/dashboards/dashboards.module').then(m => m.DashboardsModule)
},
],
canActivate: [AuthGuard]
]
},
{ 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 {
} from '@angular/material-moment-adapter';
import { CustomProgramsFakeBackendInterceptor } from './interceptors/custom-programs-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';
......@@ -111,19 +113,20 @@ export function createTranslateLoader(http: HttpClient) {
{ provide: ResourceAuthGuard, useClass: ResourceAuthGuard },
// descomentar estas lineas para OAUTH
{ provide: AuthGuard, useClass: OAuthGuard },
{ provide: AuthenticationService, useClass: OAuthAuthenticationService },
{ provide: APP_INITIALIZER, useFactory: loginLoaderFactory, deps: [AuthenticationService], multi: true },
// { provide: AuthGuard, useClass: OAuthGuard },
// { provide: AuthenticationService, useClass: OAuthAuthenticationService },
// { provide: APP_INITIALIZER, useFactory: loginLoaderFactory, deps: [AuthenticationService], multi: true },
// Para probar mantenimientos
// comentar estas lineas para OAUTH
// { provide: AuthGuard, useClass: AuthGuard},
// { provide: AuthenticationService, useClass: ByteAuthenticationService },
// { provide: HTTP_INTERCEPTORS, useClass: AuthenticationFakeBackendInterceptor, multi: true},
{ provide: AuthGuard, useClass: AuthGuard},
{ provide: AuthenticationService, useClass: ByteAuthenticationService },
{ provide: HTTP_INTERCEPTORS, useClass: AuthenticationFakeBackendInterceptor, multi: true},
{ provide: HTTP_INTERCEPTORS, useClass: SettingsFakeBackendInterceptor, multi: true },
//{ provide: HTTP_INTERCEPTORS, useClass: CustomProgramsFakeBackendInterceptor, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: AgentFakeBackendInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: CustomProgramsFakeBackendInterceptor, 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 }
......
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
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
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