Uso de elementos tarjeta en un componente

Introducción

En este tutorial añadiremos tarjetas de acceso rápido al componente home de la aplicación, el lugar que tiene la aplicación para actuar como punto de entrada de la aplicación.

Agrupar elementos del menú

Agruparemos los elementos del menú lateral dentro de una categoría para luego indicar que queremos mostrar ese menú

app.menu.config.ts

import { MenuRootItem } from 'ontimize-web-ngx';

export const MENU_CONFIG: MenuRootItem[] = [
  { id: 'home', name: 'HOME', icon: 'home', route: '/main/home' },
  {
    id: 'modules', name: 'MODULES', icon: 'remove_red_eye', opened: true,
    items: [
      { id: 'customers', name: 'CUSTOMERS', icon: 'people', route: '/main/customers' },
      { id: 'employees', name: 'EMPLOYEES', icon: 'business_center', route: '/main/employees' },
      { id: 'branches', name: 'BRANCHES', icon: 'account_balance', route: '/main/branches' },
      { id: 'accounts', name: 'ACCOUNTS', icon: 'credit_card', route: '/main/accounts' },
      { id: 'serviceEx', name: 'SERVICEEX', icon: 'dns', route: '/main/serviceEx' },
    ]
  },
  { id: 'logout', name: 'LOGOUT', route: '/login', icon: 'power_settings_new', confirm: 'yes' }
];

Una vez indicado que el menú lateral tiene algunos elementos englobados bajo un elemento padre llamado modules, podemos indicar que se muestren las tarjetas de todos los menús dentro del componente home con el componente <o-card-menu-layout>

Dentro del componente <o-card-menu-layout> podemos definir nuestras propias tarjetas con el componente <o-card-menu-item>

home.component.html

<o-card-menu-layout parent-menu-id="modules">
  <o-card-menu-item button-text="{{ 'ACCESS' | oTranslate }}" title="{{ 'DOCUMENTATION' | oTranslate }}"
    tooltip="{{ 'CHECK_DOC' | oTranslate}}" image="assets/images/ontimize.png"
    [action]="goToDocumentation"></o-card-menu-item>
</o-card-menu-layout>
o-card-menu-layout (atributos de o-card-menu-layout)
Atributos Valor Significado
parent-menu-id modules El identificador del elemento de menú cuyos hijos se renderizan. Cuando este atributo no está configurado, el componente construye una tarjeta para los elementos raíz del menú
o-card-menu-item (atributos de o-card-menu-item)
Atributos Valor Significado
button-text {{ 'ACCESS' | oTranslate }} Texto del botón de la tarjeta. Si no tiene valor, la tarjeta no tendrá botón.
title {{ 'DOCUMENTATION' | oTranslate }} Título de la tarjeta
tooltip {{ 'CHECK_DOC' | oTranslate}} Información sobre la tarjeta
image assets/images/ontimize.png Imagen de la tarjeta
action goToDocumentation Una función definida en el componente llamada cuando se hace clic en el botón de la tarjeta y el atributo de ruta no está configurado

Dentro de home.component.ts es necesario declarar la funcion que se indica en la card anterior para poder navegar a la url que nosotros establezcamos. En este caso, la documentación de Ontimize Web

home.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

@Component({
  selector: 'home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {

  constructor(
    private router: Router,
    private actRoute: ActivatedRoute
  ) {
  }

  navigate() {
    this.router.navigate(['../', 'login'], { relativeTo: this.actRoute });
  }

  goToDocumentation() {
    window.open("https://ontimizeweb.github.io/docs/v15", "_blank");
  }

}

Añadir componentes a las tarjetas

Crearemos todos los nuevos componentes que tendrán las tarjetas dentro del módulo shared

npx ng g c --skip-tests --skip-import account-card
npx ng g c --skip-tests --skip-import branch-card
npx ng g c --skip-tests --skip-import customer-card
npx ng g c --skip-tests --skip-import employee-card
npx ng g c --skip-tests --skip-import service-ex-card

Al utilizar la opción --skip-import evitaremos que el componente se importe y declare en el módulo shared. En este caso, lo que haremos será exportar una constante desde el fichero de menu (app.menu.config.ts) para que pueda ser declarada dentro del módulo de shared. También en dicho fichero, añadiremos a cada opción del menú que queramos añadir la tarjeta su correspondiente componente.

app.menu.config.ts

import { MenuRootItem } from 'ontimize-web-ngx';
import { CustomerCardComponent } from './customer-card/customer-card.component';
import { EmployeeCardComponent } from './employee-card/employee-card.component';
import { BranchCardComponent } from './branch-card/branch-card.component';
import { AccountCardComponent } from './account-card/account-card.component';
import { ServiceExCardComponent } from './service-ex-card/service-ex-card.component';

export const MENU_CONFIG: MenuRootItem[] = [
  { id: 'home', name: 'HOME', icon: 'home', route: '/main/home' },
  {
    id: 'modules', name: 'MODULES', icon: 'remove_red_eye', opened: true,
    items: [
      { id: 'customers', name: 'CUSTOMERS', icon: 'people', route: '/main/customers', component: CustomerCardComponent },
      { id: 'employees', name: 'EMPLOYEES', icon: 'business_center', route: '/main/employees', component: EmployeeCardComponent },
      { id: 'branches', name: 'BRANCHES', icon: 'account_balance', route: '/main/branches', component: BranchCardComponent },
      { id: 'accounts', name: 'ACCOUNTS', icon: 'credit_card', route: '/main/accounts', component: AccountCardComponent },
      { id: 'serviceEx', name: 'SERVICEEX', icon: 'dns', route: '/main/serviceEx', component: ServiceExCardComponent },
    ]
  },
  { id: 'logout', name: 'LOGOUT', route: '/login', icon: 'power_settings_new', confirm: 'yes' }
];

export const MENU_COMPONENTS = [
  CustomerCardComponent,
  EmployeeCardComponent,
  BranchCardComponent,
  AccountCardComponent,
  ServiceExCardComponent
];

shared.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { OntimizeWebModule } from 'ontimize-web-ngx';
import { AccountNumberRenderComponent } from '../main/accounts/accounts-home/account-number-render/account-number-render.component';
import { CustomertypeColumnRendererComponent } from '../main/customers/customers-home/customertype-column-renderer/customertype-column-renderer.component';
import { MovementColumnRendererComponent } from '../main/accounts/accounts-detail/movement-column-renderer/movement-column-renderer.component';
import { MENU_COMPONENTS } from './app.menu.config';
import { StarWarsService } from './star-wars.service';

export function intRateMonthlyFunction(rowData: Array<any>): number {
  return rowData["INTERESRATE"] / 12;
}

@NgModule({
  imports: [
    OntimizeWebModule
  ],
  declarations: [
    AccountNumberRenderComponent,
    CustomertypeColumnRendererComponent,
    MovementColumnRendererComponent,
    ...MENU_COMPONENTS
  ],
  exports: [
    CommonModule,
    AccountNumberRenderComponent,
    CustomertypeColumnRendererComponent,
    MovementColumnRendererComponent,
    ...MENU_COMPONENTS
  ]

})
export class SharedModule { }

Componente tarjeta clientes

En esta tarjeta se mostrará el número de clientes totales, y cuantos clientes hay de cada tipo. En el fichero typescript se consultará el servicio de Ontimize correspondiente a los clientes y se almacerará en variables que puedan ser mostradas desde la vista. El atributo host del componente hará que todo el componente tenga a mayores la clase que se le indica en el css, y el atributo encapsulation indica el nivel de encapsulación que tendrán las clases css.

customer-card.component.ts

import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { OntimizeService } from 'ontimize-web-ngx';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-customer-card',
  templateUrl: './customer-card.component.html',
  styleUrls: ['./customer-card.component.css'],
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.home-card]': 'true'
  }
})
export class CustomerCardComponent implements OnInit {

  public customerAmount: number;
  public vipCustomers: number;
  public normalCustomers: number;
  public otherCustomers: number;
  public basicCustomers: number;
  private subscription: Subscription;

  constructor(
    private ontimizeService: OntimizeService,
    private cd: ChangeDetectorRef,
  ) {
    this.ontimizeService.configureService(this.ontimizeService.getDefaultServiceConfiguration('customers'));
    this.subscription = this.ontimizeService.query(undefined, ['CUSTOMERID', 'CUSTOMERTYPEID'], 'customer').subscribe({
      next: (res: any) => {
        if (res.data && res.data.length) {
          this.customerAmount = res.data.length;
          this.vipCustomers = res.data.filter((e: any) => e['CUSTOMERTYPEID'] === 2).length;
          this.normalCustomers = res.data.filter((e: any) => e['CUSTOMERTYPEID'] === 1).length;
          this.otherCustomers = res.data.filter((e: any) => e['CUSTOMERTYPEID'] === 3).length;
          this.basicCustomers = this.customerAmount - this.vipCustomers - this.otherCustomers - this.normalCustomers;
        } else {
          this.customerAmount = undefined;
        }
      },
      error: (err: any) => console.log(err),
      complete: () => this.cd.detectChanges()
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

customer-card.component.html

<div fxLayout="column">
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'TOTAL_CUSTOMERS' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ customerAmount }}</p>
    </div>
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'VIP' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ vipCustomers }}</p>
    </div>
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'NORMAL' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ normalCustomers }}</p>
    </div>
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'OTHERS' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ otherCustomers }}</p>
    </div>
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'BASIC' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ basicCustomers }}</p>
    </div>
</div>

Aplicaremos este mismo estilo a todas las tarjetas, por lo que aplicaremos los estilos en el fichero styles.scss, evitando así definirlos en los *.css individuales de todas las tarjetas.

styles.scss

/* You can add global styles to this file, and also import other style files */
.home-card-title {
    color: #cccccc;
    font-weight: bold;
    text-align: end;
    margin: 0;
}

.home-card-home-amount {
    color: #202020;
    font-size: 30px;
    font-weight: bold;
    margin: 0;
    text-align: start;
}

Componente tarjeta empleados

employee-card.component.ts

import { Component, OnInit, ChangeDetectorRef, ViewEncapsulation } from '@angular/core';
import { OntimizeService } from 'ontimize-web-ngx';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-employee-card',
  templateUrl: './employee-card.component.html',
  styleUrls: ['./employee-card.component.css'],
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.home-card]': 'true'
  }
})
export class EmployeeCardComponent implements OnInit {
  public employeeAmount: number;
  private subscription: Subscription;

  constructor(
    private ontimizeService: OntimizeService,
    private cd: ChangeDetectorRef,
  ) {
    this.ontimizeService.configureService(this.ontimizeService.getDefaultServiceConfiguration('employees'));
    this.subscription = this.ontimizeService.query(undefined, ['EMPLOYEEID'], 'employee').subscribe({
      next: (res: any) => {
        if (res.data && res.data.length) {
          this.employeeAmount = res.data.length;
        } else {
          this.employeeAmount = undefined;
        }
      },
      error: (err: any) => console.log(err),
      complete: () => this.cd.detectChanges()
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

employee-card.component.html

<div fxLayout="column">
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'TOTAL_EMPLOYEES' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ employeeAmount }}</p>
    </div>
</div>

Componente tarjeta sucursales

branch-card.component.ts

import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { OntimizeService } from 'ontimize-web-ngx';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-branch-card',
  templateUrl: './branch-card.component.html',
  styleUrls: ['./branch-card.component.css'],
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.home-card]': 'true'
  }
})
export class BranchCardComponent implements OnInit {
  public branchesAmount: number;
  private subscription: Subscription;

  constructor(
    private ontimizeService: OntimizeService,
    private cd: ChangeDetectorRef
  ) {
    this.ontimizeService.configureService(this.ontimizeService.getDefaultServiceConfiguration('branches'));
    this.subscription = this.ontimizeService.query(void 0, ['OFFICEID'], 'branch').subscribe({
      next: (res: any) => {
        if (res && res.data.length) {
          this.branchesAmount = res.data.length;
        }
      },
      error: (err: any) => console.log(err),
      complete: () => this.cd.detectChanges()
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

branch-card.component.html

<div fxLayout="column">
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'TOTAL_BRANCHES' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ branchesAmount }}</p>
    </div>
</div>

Componente tarjeta cuentas

account-card.component.ts

import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { OntimizeService } from 'ontimize-web-ngx';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-account-card',
  templateUrl: './account-card.component.html',
  styleUrls: ['./account-card.component.css'],
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.home-card]': 'true'
  }
})
export class AccountCardComponent implements OnInit {
  accountAmount: any;
  private subscription: Subscription;

  constructor(
    private ontimizeService: OntimizeService,
    private cd: ChangeDetectorRef
  ) {
    this.ontimizeService.configureService(this.ontimizeService.getDefaultServiceConfiguration('branches'));
    this.subscription = this.ontimizeService.query(void 0, ['ACCOUNTID'], 'accountBalance').subscribe({
      next: (res: any) => {
        if (res && res.data.length) {
          this.accountAmount = res.data.length;
        }
      },
      error: (err: any) => console.log(err),
      complete: () => this.cd.detectChanges()
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

account-card.component.html

<div fxLayout="column">
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'TOTAL_ACCOUNT' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ accountAmount }}</p>
    </div>
</div>

Componente tarjeta servicio externo

Como estamos usando un servicio externo, no hace falta configurar el servicio de Ontimize, únicamente inyectarlo en el constructor y realizar la consulta. Añadimos en el componente el servicio StarWarsService como providers para que pueda realizar la petición

service-ex-card.component.ts

import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { StarWarsService } from '../star-wars.service';

@Component({
  selector: 'app-service-ex-card',
  templateUrl: './service-ex-card.component.html',
  styleUrls: ['./service-ex-card.component.css'],
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.home-card]': 'true'
  },
  providers: [StarWarsService]
})
export class ServiceExCardComponent implements OnInit {
  filmsAmount: number;

  constructor(
    private starwars: StarWarsService,
    private cd: ChangeDetectorRef
  ) {
    this.starwars.query({}, ['uuid'], 'films').subscribe(
      res => {
        if (res.data && res.data.length) {
          this.filmsAmount = res.data.length;
        } else {
          this.filmsAmount = undefined;
        }

      },
      err => console.log(err),
      () => this.cd.detectChanges()
    );
  }

  ngOnInit() {
  }
}

service-ex-card.component.html

<div fxLayout="column">
    <div fxLayoutAlign="space-between center" fxLayoutGap="8px">
        <p class="home-card-title" fxFlex>{{ 'TOTAL_SERVICEEX' | oTranslate }}</p>
        <p class="home-card-home-amount" fxFlex>{{ filmsAmount }}</p>
    </div>
</div>

Traducciones

Añadimos las traducciones de las tarjetas

en.json

{
  ...
  "MODULES": "Modules",
  "ACCESS": "View",
  "CHECK_DOC": "Check the OntimizeWeb documentation",
  "DOCUMENTATION": "Documentation",
  "TOTAL_CUSTOMERS": "Total customers",
  "TOTAL_EMPLOYEES": "Total employees",
  "TOTAL_BRANCHES": "Total branches",
  "TOTAL_ACCOUNT": "Total accounts",
  "TOTAL_SERVICEEX": "Total elements",
  "VIP": "VIP",
  "NORMAL": "Normal",
  "OTHERS": "Others",
  "BASIC": "Basic"
}

es.json

{
  ...
  "MODULES": "Módulos",
  "ACCESS": "Ver",
  "CHECK_DOC": "Consulta la documentación de OntimizeWeb",
  "DOCUMENTATION": "Documentación",
  "TOTAL_CUSTOMERS": "Clientes totales",
  "TOTAL_EMPLOYEES": "Empleados totales",
  "TOTAL_BRANCHES": "Sucursales totales",
  "TOTAL_ACCOUNT": "Cuentas totales",
  "TOTAL_SERVICEEX": "Elementos totales",
  "VIP": "VIP",
  "NORMAL": "Normal",
  "OTHERS": "Otros",
  "BASIC": "Básico"
}

arrow_back Tutorial anterior Próximo tutorial arrow_forward