File

projects/congarevenuecloud/elements/src/lib/product-configuration-summary/product-configuration-summary.component.ts

Description

Product configuration summary component is used to show the hierarchical view of the selected configurations for a given cart/order line item in a modal window. The view is a tree like structure containing selected attributes and values within attribute groups, selected options/attributes within option groups, nested at different levels in the hierarchy.

Preview

Usage

Example :
import { ProductConfigurationSummaryModule } from '@congarevenuecloud/elements';
@NgModule({
imports: [ProductConfigurationSummaryModule, ...]
})
export class AppModule {}
Example :
// Basic Usage
 ```typescript
<apt-product-configuration-summary [product]="product"></apt-product-configuration-summary>
Example :
// All inputs and outputs.
```typescript
<apt-product-configuration-summary
            [product]="product"
            [relatedTo]="lineItem"
            [changes]="lineItems"
            [preload]="true"
            [position]="'top'"
            (onProductAdd)="closeModal()"
            (onNavigate)="closeModal()">
</apt-product-configuration-summary>

Implements

OnChanges OnDestroy

Metadata

Index

Inputs
Outputs

Constructor

constructor(productOptionService: ProductOptionService, cartService: CartService, router: Router, exceptionService: ExceptionService, aobjectservice: AObjectService, cdr: ChangeDetectorRef, storefrontService: StorefrontService, cartItemService: CartItemService)
Parameters :
Name Type Optional
productOptionService ProductOptionService No
cartService CartService No
router Router No
exceptionService ExceptionService No
aobjectservice AObjectService No
cdr ChangeDetectorRef No
storefrontService StorefrontService No
cartItemService CartItemService No

Inputs

cart
Type : Cart

Instance of Cart

changes
Type : Array<CartItem>

List of cart items that this configuration changes.

position
Type : "left" | "right" | "middle"
Default value : 'middle'

Sets the position.

preload
Type : boolean
Default value : false

Will preload the data for the configuration component before showing to speed up initial render

product
Type : string | Product

Instance of Cart or Order line item or Product to represent the data displayed on this component.

quantity
Type : number
Default value : 1

Quantity selected of this product used for adding to cart.

relatedTo
Type : CartItem

Related cart item.

showActionButtons
Type : boolean
Default value : true

Boolean to show action buttons on configuration component.

Outputs

onNavigate
Type : EventEmitter<void>

Event emitter for when user navigates from summary.

onProductAdd
Type : EventEmitter<void>

Event emitter for when a product is added to cart.

<div bsModal #summaryModal="bs-modal" class="modal fade" [ngClass]="position" tabindex="-1" role="dialog"
  aria-labelledby="dialog-sizes-name1">
  <div class="modal-dialog modal-lg">
    <div class="modal-content" *ngIf="product$ | async as product; else loading">
      <!-- -->
      <div class="p-0" *ngIf="position === 'middle'">
        <div class="align-items-center d-flex justify-content-between flex-row-reverse">
          <button type="button" class="close close-button pull-right" aria-label="Close" (click)="hide()">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
      </div>


      <div class="modal-body bg-white">
          <h6 class="modal-title pull-left font-weight-bold" *ngIf="position === 'middle'">
            {{'COMMON.PRODUCT_CONFIGURATION' | translate}}
          </h6>
        <div class="d-md-flex d-lg-flex justify-content-between px-1 pt-3 d-none d-sm-none" *ngIf="position === 'middle'">
          <div class="flex-grow-1" id="productName">
            <h4>{{product?.Name}}</h4>
            <div class="text-muted" [translate]="'PRODUCT_CONFIGURATION_SUMMARY.PRODUCT_ID'"
              [translateParams]="{productCode: product?.ProductCode}" *ngIf="product.ProductCode"></div>
          </div>

          <div class="d-flex align-items-center">

            <div class="px-4">
              <div class="text-muted mb-2">
                {{'COMMON.NET_PRICE' | translate}}
              </div>
              <h5 class="m-0">
                <span *ngIf="changes?.length > 0; else relatedOrProductPrice">{{cartItem?.NetPrice | localCurrency | async}}</span>
                <ng-template #relatedOrProductPrice>
                 <span *ngIf="relatedTo; else productPrice">{{relatedTo?.NetPrice | localCurrency | async}}</span>
                  <ng-template #productPrice>
                    <apt-price [record]="product"
                               type="list"></apt-price>
                  </ng-template>
                </ng-template>
              </h5>
            </div>
            <div class="d-flex align-items-center">
              <div *ngIf="secondaryButton">
                <button class="border-left border-secondary btn" [ngClass]="secondaryButton?.style"
                  [ladda]="changeConfigurationLoader" data-style="zoom-in"
                  (click)="secondaryButton.action(product)">{{secondaryButton.label | translate}}</button>
              </div>
              <div *ngIf="actionButton && hideActionBtn">
                <button class="btn ml-2 btn-raised" [ngClass]="actionButton?.style"
                  (click)="actionButton.action(product)" [ladda]="addLoading" data-style="zoom-in">{{actionButton.label
                  | translate}}</button>
              </div>
            </div>
          </div>
        </div>
        <!-- Mobile view support -->
        <div class="justify-content-between pt-3 d-lg-none d-md-none d-sm-block d-block" *ngIf="position === 'middle'">
            <h4>{{product?.Name}}</h4>
            <div class="text-muted" [translate]="'PRODUCT_CONFIGURATION_SUMMARY.PRODUCT_ID'"
              [translateParams]="{productCode: product?.ProductCode}" *ngIf="product.ProductCode"></div>
        </div>
        <!-- Mobile view support -->
        <div class="d-sm-flex d-flex justify-content-between pt-3 d-lg-none d-md-none" *ngIf="position === 'middle'">
          <div class="pr-4">
              <div class="text-muted mb-1">
                {{'COMMON.NET_PRICE' | translate}}
              </div>
              <h5 class="m-0">
                <span *ngIf="changes?.length > 0; else relatedOrProductPrice">{{cartItem?.NetPrice | localCurrency | async}}</span>
                <ng-template #relatedOrProductPrice>
                 <span *ngIf="relatedTo; else productPrice">{{relatedTo?.NetPrice | localCurrency | async}}</span>
                  <ng-template #productPrice>
                    <apt-price [record]="product"
                               type="list"></apt-price>
                  </ng-template>
                </ng-template>
              </h5>
          </div>
          <div *ngIf="secondaryButton" class="pt-1">
            <button class="border-left border-secondary btn btn-sm" [ngClass]="secondaryButton?.style"
              [ladda]="changeConfigurationLoader" data-style="zoom-in"
              (click)="secondaryButton.action(product)">{{secondaryButton.label | translate}}</button>
          </div>
          <div *ngIf="actionButton && hideActionBtn" class="pt-1">
            <button class="btn ml-2 btn-raised btn-sm" [ngClass]="actionButton?.style"
              (click)="actionButton.action(product)" [ladda]="addLoading" data-style="zoom-in">{{actionButton.label
              | translate}}</button>
          </div>
        </div>
        <div class="p-0" *ngIf="position === 'right'">
          <div class="align-items-center border-bottom border-secondary d-flex pb-3">
            <h5 class="modal-title font-weight-normal">
              <div>{{'COMMON.PRODUCT_CONFIGURATION' | translate}}</div>
            </h5>
            <div *ngIf="relatedTo">
              <select class="form-control ml-3 form-control-sm" id="summary-filter" name="summaryFilter"
                [(ngModel)]="filter" (ngModelChange)="setProduct()">
                <option [value]="'items'">{{'COMMON.ALL' | translate}}</option>
                <option [value]="'changes'">{{'COMMON.CHANGES_ONLY' | translate}}</option>
              </select>
            </div>
            <button type="button" class="close ml-auto pt-1" aria-label="Close" (click)="hide()">
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
  
          <div>
            <div class="bg-light p-3 line-height-large">
              <div class="d-flex justify-content-between">
                <strong class="col-7 pl-0">{{product?.Name}}</strong>
                <div class="col-2">{{'COMMON.QTY' | translate}}: {{product?._metadata?.item?.Quantity? product?._metadata?.item?.Quantity : 1}}</div>
                <span>{{product?._metadata?.item?.BaseExtendedPrice | localCurrency | async}}</span>
              </div>
              <div class="d-flex justify-content-between">
                <span>{{'COMMON.OPTION_TOTAL' | translate}}</span>
                <span>{{product?._metadata?.item?.OptionPrice | localCurrency | async}}</span>
              </div>
              <div class="d-flex border-top border-gray justify-content-between pt-3">
                <strong>{{'COMMON.NET_PRICE' | translate}}</strong>
                <span>{{product?._metadata?.item?.NetPrice | localCurrency | async}}</span>
              </div>
            </div>
          </div>
  
          <h5 class="font-weight-normal mt-4">
            {{'COMMON.ITEMIZED_OPTIONS' | translate}}
          </h5>
        </div>
        <ng-container *ngIf="product?.OptionGroups?.length > 0 || product?.AttributeGroups?.length > 0; else empty">
          <div>
            <!-- Header -->


            <!-- Main Accordion -->

            <div class="accordion mt-3" [id]="uuid + product?.Id"
              *ngIf="product?.AttributeGroups?.length > 0 || product?.OptionGroups?.length > 0">

              <!-- Top Level Attributes -->
              <ng-container
                *ngFor="let group of product?.AttributeGroups; let x = index; let xf = first; trackBy: trackById">
                <ng-container *ngIf="group?.AttributeGroup?.AttributeGroupMembers?.length > 0">
                  <div class="bg-light border-top border-secondary">
                    <button class="btn btn-link chevron" type="button" data-toggle="collapse"
                      [attr.data-target]="'#' + uuid + group.Id" [attr.aria-expanded]="xf">
                      <strong class="ml-2">{{group?.AttributeGroup?.Name}}</strong>
                    </button>

                  </div>

                  <div [id]="uuid + group.Id" class="collapse" [class.show]="xf"
                    [attr.data-parent]="'#' + uuid + product?.Id">
                    <ng-container *ngFor="let member of group?.AttributeGroup?.AttributeGroupMembers; let l = last">
                      <div class="pt-2 px-3" [class.border-bottom]="!l" [class.border-gray]="!l"
                        *ngIf="product.get('item')?.AttributeValue[member.Attribute.Name]">
                        <apt-output-field [record]="product?._metadata?.item?.AttributeValue"
                          [field]="member.Attribute.Name" [displayValue]="member.Attribute.Name" [editable]="false"
                          [label]="member.Attribute.DisplayName" labelClass="font-italic font-weight-normal"
                          valueClass="font-weight-bold">
                        </apt-output-field>
                      </div>
                    </ng-container>
                  </div>
                </ng-container>
              </ng-container>

              <!-- Top Level Options-->
              <div
                *ngFor="let optionGroupMember of product.OptionGroups; let f = first; let i = index; trackBy: trackById">
                <ng-container *ngIf="!optionGroupMember?.IsHidden">
                  <div class="bg-light border-top border-secondary">
                    <button class="btn btn-link chevron" type="button" data-toggle="collapse"
                      [attr.data-target]="'#' + uuid + optionGroupMember.Id"
                      [attr.aria-expanded]="i + product?.AttributeGroups?.length === 0">
                      <strong class="ml-2">{{optionGroupMember?.OptionGroup?.Label}}</strong>
                    </button>
                  </div>
                  <div class="collapse" [id]="uuid + optionGroupMember.Id" [attr.data-parent]="'#' + uuid + product?.Id"
                    [class.show]="i + product?.AttributeGroups?.length === 0">
                    <div class="card-body">
                      <div class="accordion" [id]="'child' + uuid + optionGroupMember.Id">
                        <div *ngFor="let productOptionGroup of optionGroupMember?.ChildOptionGroups; trackBy: trackById"
                          class="mb-3">
                          <ng-template *ngIf="!productOptionGroup?.IsHidden"
                            [ngTemplateOutlet]="productOptionGroupTemplate"
                            [ngTemplateOutletContext]="{productOptionGroup: productOptionGroup, parent: 'child' + uuid + optionGroupMember.Id}">
                          </ng-template>
                        </div>

                        <div *ngFor="let option of optionGroupMember?.Options; let f = first; trackBy: trackById"
                          class="mb-3">
                          <ng-template [ngTemplateOutlet]="optionTemplate" [ngTemplateOutletContext]="{option: option
                                  ,parent: 'child' + uuid + optionGroupMember.Id
                                  ,expanded: f
                                  , cartItem: option?.ComponentProduct?._metadata?.item}">
                          </ng-template>
                        </div>
                      </div>
                    </div>
                  </div>
                </ng-container>
              </div>

              <ng-template #productOptionGroupTemplate let-productOptionGroup="productOptionGroup" let-parent="parent">
                <button class="btn btn-link p-0 minus text-capitalize text-dark" data-toggle="collapse"
                  [attr.data-target]="'#' + parent + productOptionGroup.Id" [attr.aria-expanded]="true">
                  <i>{{productOptionGroup?.OptionGroup?.Label}}</i>
                </button>

                <div [id]="parent + productOptionGroup.Id" class="collapse" [class.show]="true"
                  [attr.data-parent]="'#' + parent">
                  <div class="my-2 ml-3"
                    *ngFor="let component of productOptionGroup?.Options; let f = first; trackBy: trackById">
                    <ng-template [ngTemplateOutlet]="optionTemplate"
                      [ngTemplateOutletContext]="{option: component, parent: parent + productOptionGroup.Id, expanded: f, cartItem: component?.ComponentProduct?._metadata?.item}">
                    </ng-template>
                  </div>
                </div>
              </ng-template>

              <!-- Nested option template -->
              <ng-template #optionTemplate let-option="option" let-parent="parent" let-expanded="expanded"
                let-cartItem="cartItem">
                <div class="d-flex justify-content-between border-bottom border-gray"
                  *ngIf="cartItem?.AssetStatus !== 'Cancelled'">
                  <div class="d-flex align-items-center text-truncate mb-1">
                    <button class="btn btn-link p-0 minus text-capitalize text-dark text-truncate btn-sm"
                      data-toggle="collapse" [attr.data-target]="'#' + parent + option?.Id"
                      [attr.aria-expanded]="expanded"
                      *ngIf="option?.ComponentProduct?.OptionGroups?.length > 0 || option?.ComponentProduct?.AttributeGroups?.length > 0; else read">
                      {{option?.ComponentProduct?.Name}}
                    </button>
                    <ng-template #read>
                      <div class="text-dark text-truncate">
                        {{option?.ComponentProduct?.Name}}
                      </div>
                    </ng-template>
                    <ng-template [ngTemplateOutlet]="statusBadge" [ngTemplateOutletContext]="{cartItem: cartItem}"
                      *ngIf="relatedTo?.AssetLineItem?.Id || isOrderLineItem || isQuoteLineItem"></ng-template>
                  </div>
                  <div class="d-flex flex-nowrap justify-content-between width-fixed mx-2">
                    <ng-container *ngIf="cartItem && cartItem?.NetPrice; else productPrice">
                      <div>
                        {{'COMMON.QTY' | translate}}: {{cartItem?.Quantity}}
                      </div>
                      <div class="ml-auto">
                        <ng-container *ngIf="(isOrderLineItem || isQuoteLineItem); else itemPrice">
                          <!-- TO DO:NetPrice should come as object right now in QuoteLineItem it's not the same (CPQ-81002)  -->
                          <div>{{ cartItem?.NetPrice?.Value || cartItem?.NetPrice | localCurrency | async }}</div>
                        </ng-container>
                        <ng-template #itemPrice>
                          <span  *ngIf="!(isOrderLineItem || isQuoteLineItem)">{{cartItem?.NetPrice | localCurrency | async}}</span>
                        </ng-template>
                      </div>
                    </ng-container>
                    <ng-template #productPrice>
                      <div>
                        {{'COMMON.QTY' | translate}}: {{(option?.DefaultQuantity) ? (option?.DefaultQuantity) : 1}}
                      </div>
                      <div class="ml-auto">
                        <apt-price [record]="option?.ComponentProduct" [type]="'list'"></apt-price>
                      </div>
                    </ng-template>
                  </div>
                </div>

                <!-- Option Accordion -->
                <div [id]="parent + option?.Id" [attr.data-parent]="'#' + parent"
                  class="collapse pl-4 configuration accordion" [class.show]="expanded">
                  <div
                    *ngFor="let nestedGroups of option?.ComponentProduct?.AttributeGroups; let aIndex = index; let aFirst = first; trackBy: trackById"
                    class="mt-2">
                    <button class="btn btn-link p-0 minus text-capitalize text-dark" data-toggle="collapse"
                      [attr.data-target]="'#' + 'ac' + option?.Id + nestedGroups.Id" [attr.aria-expanded]="true">
                      <i>{{nestedGroups?.AttributeGroup?.Name}}</i>
                    </button>

                    <div [id]="'ac' +option?.Id + nestedGroups.Id" class="collapse" [class.show]="true"
                      [attr.data-parent]="'#' + parent + option?.Id">
                      <div *ngFor="let member of nestedGroups?.AttributeGroup?.AttributeGroupMembers; let l = last">
                        <div class="pt-2 px-3 border-bottom border-gray"
                          *ngIf="cartItem?.AttributeValue && cartItem?.AttributeValue[member?.Attribute?.Name]">
                          <apt-output-field [record]="cartItem?.AttributeValue" [field]="member?.Attribute.Name"
                            [displayValue]="member?.Attribute?.Name" [editable]="false"
                            [label]="member?.Attribute?.DisplayName" labelClass="font-italic font-weight-normal"
                            valueClass="font-weight-bold">
                          </apt-output-field>
                        </div>
                      </div>
                    </div>
                  </div>

                  <div
                    *ngFor="let productOptionGroup of option.ComponentProduct?.OptionGroups; let oFirst = first; trackBy: trackById"
                    class="mt-3">
                    <ng-template *ngIf="!productOptionGroup?.IsHidden" [ngTemplateOutlet]="productOptionGroupTemplate"
                      [ngTemplateOutletContext]="{productOptionGroup: productOptionGroup, parent: parent + option.Id}">
                    </ng-template>
                  </div>
                </div>

              </ng-template>
            </div>
          </div>
        </ng-container>

        <ng-template #empty>
          <div class="d-flex justify-content-center flex-column align-items-center py-5 my-5">
            <i class="fa fa-cog fa-5x text-primary xl text-faded"></i>
            <div class="mt-4">{{'CONFIGURATION.EMPTY' | translate}}</div>
          </div>
        </ng-template>
      </div>
    </div>

    <ng-template #loading>
      <div class="modal-content">
        <div class="modal-body">
          <div class="d-flex justify-content-center py-5">
            <apt-dots></apt-dots>
          </div>
        </div>
      </div>
    </ng-template>

  </div>
</div>

<ng-template #statusBadge let-cartItem="cartItem">
  <ng-container [ngSwitch]="cartItem?.LineStatus">
    <span class="badge ml-2 badge-danger py-1" *ngSwitchCase="'Cancelled'">
      {{cartItem?.LineStatus}}
    </span>
    <span class="badge ml-2 badge-info py-1" *ngSwitchCase="'Upgraded'">
      {{cartItem?.LineStatus}}
    </span>
    <span class="badge ml-2 badge-warning py-1" *ngSwitchCase="'Amended'">
      {{cartItem?.LineStatus}}
    </span>
    <span class="badge ml-2 badge-success py-1" *ngSwitchCase="'New'">
      {{cartItem?.LineStatus}}
    </span>
    <span class="badge ml-2 badge-warning py-1" *ngSwitchCase="'Renewed'">
      {{cartItem?.LineStatus}}
    </span>
    <span class="badge ml-2 badge-light py-1" *ngSwitchCase="'Existing'">
      {{cartItem?.LineStatus}}
    </span>
  </ng-container>
</ng-template>

./product-configuration-summary.component.scss

:host {
  font-size: small;
}
.configuration {
  font-size: small;
}
.line-height-large {
  line-height: 1.8rem;
}
.modal {
  &.right {
    .modal-content {
      height: 100vh;
    }
    &.show {
      .modal-dialog {
        right: -1px;
      }
    }
    .modal-dialog {
      position: absolute;
      transition: right 300ms;
      margin: -1px 0 0 0;
      right: -20rem;
      width: 40rem;
      max-width: 100vw;
      top: 0;
      .modal-content {
        height: 100vh;
      }
    }
  }
}

.width-fixed {
  min-width: 8rem;
  max-width: 8rem;
}

.option-item {
  padding-left: 1.5rem;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""