Commit be08330d authored by Aaron Gutierrez's avatar Aaron Gutierrez

cambios para dashboard hdi

parent 3f8c4b35
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomLayoutComponent, NotFoundComponent } 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';
......@@ -9,29 +9,20 @@ 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: 'configuration', data: { breadcrumb: 'Agentes' }, canLoad: [AuthGuard],
loadChildren: () => import('./modules/agent/agent.module').then(m => m.AgentModule)
},
{ path: 'home', component: HomeComponent, data: { breadcrumb: 'Home' } }
]
},
{ path: '', component: BaseLayoutComponent,
children: [
{
path: 'dashboards', data: { breadcrumb: 'Dashboards' }, canLoad: [AuthGuard],
path: 'dashboards', data: { breadcrumb: 'Dashboards' },
loadChildren: () => import('./modules/dashboards/dashboards.module').then(m => m.DashboardsModule)
},
],
canActivate: [AuthGuard]
]
},
{ path: 'notpermitted', component: NotFoundComponent},
......
......@@ -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';
......@@ -124,6 +126,7 @@ export function createTranslateLoader(http: HttpClient) {
{ 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: 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 } });
}
}
}
import { Component, 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 {
subscriptionToogle: Subscription;
constructor(
//protected toogleService: ToogleService
) {
}
ngOnDestroy(): void {
if (this.subscriptionToogle) {
this.subscriptionToogle.unsubscribe();
}
}
chart: Chart;
// chart = new Chart({
// chart: {
// type: 'spline',
// marginRight: 10,
// events: {
// load: function () {
// // set up the updating of the chart each second
// const series = this.series[0];
// setInterval(function () {
// const x = (new Date()).getTime(), // current time
// y = Math.random();
// series.addPoint([x, y], true, true);
// }, 1000);
// }
// }
// },
// title: {
// text: 'Live random data'
// },
// xAxis: {
// type: 'datetime',
// tickPixelInterval: 150
// },
// yAxis: {
// title: {
// text: 'Value'
// },
// plotLines: [{
// value: 0,
// width: 1,
// color: '#808080'
// }]
// },
// tooltip: {
// formatter: function () {
// return '<b>' + this.series.name + '</b><br/>' +
// Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
// Highcharts.numberFormat(this.y, 2);
// }
// },
// legend: {
// enabled: false
// },
// exporting: {
// enabled: false
// },
// series: [{
// name: 'Random data',
// data: (function () {
// // generate an array of random data
// const data = [],
// time = (new Date()).getTime();
// let i: any;
// for (i = -19; i <= 0; i += 1) {
// data.push({
// x: time + i * 1000,
// y: Math.random()
// });
// }
// return data;
// }())
// }] as Array<any>
// });
ngOnInit() {
this.chart = new Chart({
chart: {
type: 'heatmap',
events: {
load: function(event:any) {
event.target.reflow();
}
}
},
boost: {
useGPUTranslations: true
},
credits: {
enabled: false
},
title: {
text: 'Cantidad de mensajes por horario de actividad del cliente',
align: 'center',
x: 40
},
xAxis: {
type: 'datetime',
min: Date.UTC(2020, 0, 1),
max: Date.UTC(2020, 0, 10, 59, 59, 59),
labels: {
align: 'left',
x: 5,
y: 14,
format: '{value:%d/%m %H:%M}' // long month
},
showLastLabel: false,
tickLength: 10,
tickWidth: 1
},
yAxis: {
title: {
text: null
},
labels: {
formatter: function () {
return 'H' + (this.value + 1) + ': ' + (3*this.value < 10 ? '0' : '') + 3*this.value + ':00' + ' - ' + ((this.value + 3) < 10 ? '0' : '') + (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, '#1c84c6'],
[0.5, '#23c6c8'],
[1, '#E1F5B1']
],
min: -15,
max: 25,
startOnTick: false,
endOnTick: false,
labels: {
format: '{value}'
}
},
series: [{
boostThreshold: 100,
borderWidth: 0,
nullColor: '#DDD',
borderColor: '#ddd',
colsize: 24 * 36e5, // one day
tooltip: {
headerFormat: '{point.x:%e %b}',
pointFormat: '{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>'
},
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
data: [
[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
[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]
]
}] as Array<any>
});
// this.subscriptionToogle = this.toogleService.changeEmitted$.subscribe(change => {
// setTimeout(() => {
// Highcharts.charts.forEach(chart => chart.reflow());;
// console.log("test");
// }, 500);
// });
}
}
<!-- <div class="headerpanel border-bottom">
<div class="row">
<div class="col-12">
<div class="subheader">
<h1 class="subheader-title" style="padding-top: 5px;">
<i class="subheader-icon fa fa-area-chart"></i> Panel de gestión de tickets
</h1>
<div class="pull-right" style="text-align: right;">
<span class="header-date">{{ latestDate }}</span>
<br>
<b>Última actualización: </b><span class="header-date">{{ latestDate }}</span>
</div>
</div>
</div>
</div>
</div> -->
<div class="row">
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-inactivity-sesion header="Inactividad de la sesion" [value]="inactivitySession" [options]="optionsInactivitySesion"></byte-inactivity-sesion>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="Total de sesiones" [value]="totalSesions" [options]="totalSesionsOptions"></byte-summary-peti-widget>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="Total de mensajes recibidos" [value]="totalReceivedMessages" [options]="totalReceivedOptions"></byte-summary-peti-widget>
</div>
<div class="col-lg-3 col-sm-6 col-xs-12">
<byte-summary-peti-widget header="Total de mensajes enviados" [value]="totalSentMessages" [options]="totalSentOptions"></byte-summary-peti-widget>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="ibox" style="padding-top: 15px;">
<div class="ibox-content b-s" style="text-align: center;">
<byte-gauge-chart name="Tiempo promedio de respuestas" style="display: inline-block;"></byte-gauge-chart>
</div>
</div>
</div>
<div class="col-6">
<div class="ibox" style="padding-top: 15px;">
<div class="ibox-content b-s" style="text-align: center;">
<byte-gauge-chart name="Tiempo promedio de respuestas" style="display: inline-block;"></byte-gauge-chart>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<xdf-widget-panel [options]="panelOptions">
<div class="row">
<div class="col-12">
<byte-heat-map></byte-heat-map>
</div>
</div>
</xdf-widget-panel>
</div>
</div>
\ 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;
}
.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;
}
\ No newline at end of file
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from '@xdf/security';
import { DirtyGuard, TemplateResolver, CrudGridComponent, EditableDataTableTemplateResolver, CrudDetailComponent, FormViewComponent, ListResolver, RecordResolver } from '@xdf/gallery';
import { ResourceAuthGuard } from '@xdf/security';
import { ApplicationFormComponent, ValposFormComponent } from '@xdf/settings';
import { TabsLayoutComponent, TabsTemplateResolver } from '@xdf/layouts';
import { OperativeDashboardComponent } from './components/operative-dashboard/operative-dashboard.component';
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,
path: 'operative', component: OperativeDashboardComponent,
data: {
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Operativo'
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'
}
}
];
......
......@@ -4,28 +4,45 @@ 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 } from '@angular/material';
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 './components/operative-dashboard/operative-dashboard.component';
import { SummaryPetiWidgetComponent } from './components/summary-peti-widget/summary-peti-widget.component';
import { CustomerActivityWidgetComponent } from './components/customer-activity-widget/customer-activity-widget.component';
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 './components/widget-peti/widget-peti.component';
import { GaugeChartComponent } from './components/gauge-chart/gauge-chart.component';
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 './components/inactivity-sesion/inactivity-sesion.component';
import { HeatMapComponent } from './components/heat-map/heat-map.component'
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];
return [ more, exporting, highmaps, sankey];
}
@NgModule({
......@@ -36,7 +53,17 @@ export function highchartsModules() {
WidgetPetiComponent,
GaugeChartComponent,
InactivitySesionComponent,
HeatMapComponent],
HeatMapComponent,
OperativeDashboardFilterComponent,
StackedColumnChartComponent,
CustomerInteractionComponent,
CustomerInteractionFilterComponent,
SankeyDiagramComponent,
MessageByIntentTableComponent,
UnidentifiedSentencesTableComponent,
AvgIntentByCustomerComponent,
MillisToDayHourMinuteSecond,
TracingIntentByCustomerComponent],
imports: [
CommonModule,
FormsModule,
......@@ -54,7 +81,19 @@ export function highchartsModules() {
XdfGraphModule,
GaugeChartModule,
NgxChartsModule,
ChartsModule
ChartsModule,
MatDatepickerModule,
NgxMatDatetimePickerModule,
NgxMatTimepickerModule,
NgxMatNativeDateModule,
MatInputModule,
LocalizedDateModule,
MatTabsModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatIconModule,
NgbModule
],
entryComponents: [
],
......
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();
}
}
<rg-gauge-chart
[canvasWidth]="canvasWidth"
[needleValue]="value"
[centralLabel]="centralLabel"
[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 { DecimalPipe } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
export class GaugeChart {
......@@ -12,12 +13,12 @@ export class GaugeChart {
export const DEFAULT_OPTIONS_GAUGE_CHART: GaugeChart = {
hasNeedle: true,
needleColor: '#5D2418',
needleColor: '#676a6c',
needleUpdateSpeed: 1000,
arcColors: ['#1ab394', '#f8ac59', '#ed5565'],
arcDelimiters: [20, 50],
rangeLabel: ['0', '100'],
needleStartValue: 50,
arcDelimiters: [16, 33],
rangeLabel: ['0', '6'],
needleStartValue: 1,
}
@Component({
......@@ -34,19 +35,46 @@ export class GaugeChartComponent implements OnInit {
value: any = 1.2;
@Input()
unit: string = "s";
unit: string = "";
@Input()
options: GaugeChart;
canvasWidth = 300
centralLabel = ''
@Input()
format: Boolean = false;
@Input()
maxValue: number = 6;
canvasWidth = 250
centralLabel = '25'
bottomLabel = '';
constructor() { }
ngOnInit() {
this.bottomLabel = this.value + ' ' + this.unit;
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 { 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);
// });
}
}
......@@ -2,18 +2,20 @@
<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 }}
{{ 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">
{{ value?.value | number:'1.0':'en-US' }}
<span class="text-170 text-secondary-d4" style="font-size: 1.3em !important;">
{{ value?.value | millisToDayHourMinuteSecond }}
</span>
<small>min</small>
</div>
</div>
<!-- <div class="session-icon">
<i class="fa fa-2x fa-clock-o" matTooltip="{{ 'action.grid.refresh' | translate }}"></i>
</div> -->
</div>
......
import { Component, HostListener, Input, OnInit, ViewChild } from '@angular/core';
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';
......@@ -7,7 +7,7 @@ import { PetyWidgetModel } from '../summary-peti-widget/summary-peti-widget.comp
templateUrl: './inactivity-sesion.component.html',
styleUrls: ['./inactivity-sesion.component.scss']
})
export class InactivitySesionComponent implements OnInit {
export class InactivitySesionComponent implements OnInit, AfterViewInit {
@Input() options = {
width: 200,
......@@ -28,18 +28,33 @@ export class InactivitySesionComponent implements OnInit {
ngOnInit() {
}
ngAfterViewInit() {
if (this.peti) {
const width = window.innerWidth;
this.updateWidth(width);
this.peti.updateSize();
}
}
@HostListener('window:resize',['$event'])
public onResize(event) {
console.log("asdasdasdsad");
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 >= 990){
} else if (width >= 768){
this.options.width = 150;
} else if (width >= 576){
this.options.width = 200;
} else {
this.options.width = 300;
}
this.peti.updateSize();
}
}
<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 { 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({
......@@ -9,12 +14,19 @@ import { PetyWidgetModel } from '../summary-peti-widget/summary-peti-widget.comp
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: 'Primary1',
secondary: 'Secondary'
primary: 'dashboards.operative.message.customer.activity',
secondary: ''
}
}
......@@ -26,32 +38,6 @@ export class OperativeDashboardComponent implements OnInit {
strokeWidth: 1
};
inactivitySession: any = {
value: 30,
history: [10, 10, 10, 10, 10, 10, 10],
percent: 2,
up: true
};
totalReceivedMessages: PetyWidgetModel = {
value: 120000,
history: [1, 5, 3, 8, 4, 10, 0],
percent: 2,
up: true
};
totalSentMessages: PetyWidgetModel = {
value: 120000,
history: [10, 8, 6, 5, 6, 8, 10],
percent: 2,
up: true
};
totalSesions: PetyWidgetModel = {
value: 120000,
history: [10, 8, 6, 5, 6, 8, 10],
percent: 2,
up: true
};
totalSesionsOptions = {
width: 300,
height: 60,
......@@ -76,9 +62,27 @@ export class OperativeDashboardComponent implements OnInit {
strokeWidth: 1
};
constructor() { }
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
......@@ -2,28 +2,28 @@
<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 }}
{{ header | translate }}&nbsp;<i class="fa fa-info-circle" matTooltip="{{ tooltip | translate }}"></i>
</div>
<div class="lh">
<span class="text-170 text-secondary-d4">
{{ value?.value | number:'1.0':'en-US' }}
{{ formatterValue }}
</span>
<span class="text-blue text-nowrap ml-n1">
<!-- <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>
</span> -->
</div>
</div>
</div>
<div class="align-self-center">
<xdf-peity [values]="value?.history" [type]="'line'" theme="primary" [attributes]='options'></xdf-peity>
<xdf-peity #peti [values]="value?.history" [type]="'line'" theme="primary" [attributes]='options'></xdf-peity>
</div>
</div>
......@@ -10,14 +10,14 @@
color: #60626a!important;
}
.text-100 {
font-size: 1em!important;
font-size: 1em;
}
.text-secondary-d4 {
color: #4c5b70!important;
}
.text-170 {
font-size: 1.7em!important;
font-size: 1.7em;
}
.text-blue {
......
import { Component, HostListener, Input, OnInit } from '@angular/core';
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 {
......@@ -26,15 +28,53 @@ export class SummaryPetiWidgetComponent implements OnInit {
@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() {
}
@HostListener('window:resize')
public onResize() {
jQuery('')
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;
}
}
}
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;
}
}
[
{
"id": 30,
"name": "security",
"label": "menu.security",
"icon": "<i class=\"fa fa-address-book\"></i>",
"fullPath": "/security",
"singlePath": "security",
"id": 38,
"name": "BBOT_DASHBOARD",
"label": "menu.parent.dashboard",
"icon": "<i class=\"fa fa-area-chart\"></i>",
"fullPath": "/dashboards",
"singlePath": "dashboards",
"isProgram": false,
"children": [
{
"id": 32,
"name": "user",
"label": "menu.security.user",
"name": "operative_dashboard",
"label": "menu.dashboard.operative",
"icon": null,
"fullPath": "/security/user",
"singlePath": "user",
"fullPath": "/dashboards/operative",
"singlePath": "operative",
"isProgram": true,
"children": []
},
{
"id": 32,
"name": "user_role",
"label": "menu.security.user.role",
"name": "customer_interaction_dashboard",
"label": "menu.dashboard.customer.interaction",
"icon": null,
"fullPath": "/security/user-role",
"singlePath": "user-role",
"fullPath": "/dashboards/customer-interaction",
"singlePath": "customer-interaction",
"isProgram": true,
"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": []
},
{
"id": 37,
"name": "OPERATIVE_DASHBOARD",
"label": "menu.parent.dashboard",
"icon": "<i class=\"fa fa-area-chart\"></i>",
"fullPath": "/dashboards/operative",
"singlePath": "operative",
"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
......@@ -8,5 +8,6 @@
"motivos",
"categoria_motivo",
"CONVERSATIONAL_AGENT",
"OPERATIVE_DASHBOARD"
"OPERATIVE_DASHBOARD",
"customer_interaction_dashboard"
]
\ No newline at end of file
......@@ -71,5 +71,31 @@
"label.file-upload.error.description": "Ha ocurrido un error en la validación del archivo.",
"btn.retry": "Cargar otro archivo",
"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
......@@ -13,5 +13,7 @@
"menu.business.config": "Configuración",
"menu.ticket.manager": "Gestor de tickets",
"menu.parent.agent": "Agentes",
"menu.parent.dashboard": "Dashboard"
"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