File

projects/apttus/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

 import { ProductConfigurationSummaryModule } from '@apttus/elements';

 @NgModule({
  imports: [ProductConfigurationSummaryModule, ...]
})
 export class AppModule {}

Implements

OnChanges OnDestroy

Example

// Basic Usage
<apt-product-configuration-summary [product]="product"></apt-product-configuration-summary>

// All inputs and outputs.
<apt-product-configuration-summary
[product]="product"
[relatedTo]="lineItem"
[changes]="lineItems"
[preload]="true"
[position]="'top'"
(onProductAdd)="closeModal()"
(onNavigate)="closeModal()">
</apt-product-configuration-summary>

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector apt-product-configuration-summary
styleUrls ./product-configuration-summary.component.scss
templateUrl ./product-configuration-summary.component.html

Index

Inputs
Outputs

Constructor

constructor(productOptionService: ProductOptionService, cartService: CartService, router: Router, cacheService: CacheService, ngZone: NgZone, exceptionService: ExceptionService)
Parameters :
Name Type Optional
productOptionService ProductOptionService No
cartService CartService No
router Router No
cacheService CacheService No
ngZone NgZone No
exceptionService ExceptionService No

Inputs

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 | BundleProduct

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.

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 overflow-auto" *ngIf="product$ | async as product; else loading">


      <!-- -->
      <div class="p-0 mx-4 mt-3" *ngIf="position === 'middle'">
        <div class="align-items-center border-bottom border-secondary d-flex justify-content-between pb-3">
          <h5 class="modal-title font-weight-normal">
            {{'COMMON.PRODUCT_CONFIGURATION' | translate}}
          </h5>
          <button type="button" class="close" aria-label="Close" (click)="hide()">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>


        <div class="bg-light d-flex justify-content-between p-3">
          <div class="flex-grow-1">
            <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="border-left border-secondary d-flex align-items-center">
            <div class="px-4">
              <div class="text-muted mb-2">
                {{'COMMON.NET_PRICE' | translate}}
              </div>
              <h5 class="m-0">
                <apt-price *ngIf="changes?.length > 0; else relatedOrProductPrice"
                           [record]="changes"
                           [bundle]="true"
                           [quantity]="quantity"
                           type="net"></apt-price>
                <ng-template #relatedOrProductPrice>
                  <apt-price *ngIf="relatedTo; else productPrice"
                             [record]="relatedTo"
                             [quantity]="quantity"
                             [bundle]="true"
                             type="net"></apt-price>

                  <ng-template #productPrice>
                    <apt-price [record]="product"
                               [quantity]="quantity"
                               [bundle]="true"
                               type="net"></apt-price>
                  </ng-template>
                </ng-template>
              </h5>
            </div>
            <div class="d-flex align-items-center">
              <div *ngIf="secondaryButton">
                <button class="btn" [ngClass]="secondaryButton?.style"
                  (click)="secondaryButton.action(product)">{{secondaryButton.label | translate}}</button>
              </div>
              <div *ngIf="actionButton">
                <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>
      </div>

      <div class="p-0 mx-4 mt-3" *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" 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="row">
              <strong class="col-7">{{product?.Name}}</strong>
              <div class="col-2">{{'COMMON.QTY' | translate}}: 1</div>
              <apt-price class="col-3 text-right" [record]="product?._metadata?.item" [type]="'net'"></apt-price>
            </div>
            <div class="d-flex justify-content-between">
              <span>{{'COMMON.OPTION_TOTAL' | translate}}</span>
              <apt-price [record]="optionItems" [type]="'net'"></apt-price>
            </div>
            <div class="d-flex border-top border-gray justify-content-between pt-3">
              <strong>{{'COMMON.NET_PRICE' | translate}}</strong>
              <apt-price [record]="cartItems" [type]="'net'"></apt-price>
            </div>
          </div>
        </div>

        <h5 class="font-weight-normal mt-4">
          {{'COMMON.ITEMIZED_OPTIONS' | translate}}
        </h5>
      </div>


      <div class="modal-body px-0 pt-0 mx-4">
        <ng-container *ngIf="product?.OptionGroups?.length > 0 || product?.AttributeGroups?.length > 0; else empty">
          <div class="h-100">
            <!-- 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 attributeGroupMember of product?.AttributeGroups; let x = index; let xf = first; trackBy: trackById">
                <ng-container *ngIf="attributeGroupMember?.AttributeGroup?.ProductAttributes?.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 + attributeGroupMember.Id" [attr.aria-expanded]="xf">
                      <strong class="ml-2">{{attributeGroupMember?.AttributeGroup?.Name}}</strong>
                    </button>

                  </div>

                  <div [id]="uuid + attributeGroupMember.Id" class="collapse" [class.show]="xf"
                    [attr.data-parent]="'#' + uuid + product?.Id">
                    <ng-container
                      *ngFor="let attribute of attributeGroupMember?.AttributeGroup?.ProductAttributes; let l = last">
                      <div class="pt-2 px-3" [class.border-bottom]="!l" [class.border-gray]="!l" *ngIf="product.get('item')?.AttributeValue[attribute.Field]">
                        <apt-output-field [record]="product?._metadata?.item?.AttributeValue" [field]="attribute.Field"
                          [editable]="false" 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?.Name}}</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?.Name}}</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 || isOrderLineItem || isQuoteLineItem"></ng-template>
                  </div>
                  <div class="d-flex flex-nowrap justify-content-between width-fixed mx-2">
                    <ng-container *ngIf="cartItem; else productPrice">
                      <div>
                        {{'COMMON.QTY' | translate}}: {{cartItem?.Quantity}}
                      </div>
                      <div class="ml-auto">
                        <apt-price [record]="cartItem" [quantity]="cartItem?.Quantity"></apt-price>
                      </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"
                          [quantity]="(option?.DefaultQuantity) ? (option?.DefaultQuantity) : 1"></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 attributeGroupMember 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]="'#' + option?.Id + attributeGroupMember.Id" [attr.aria-expanded]="true">
                      <i>{{attributeGroupMember?.AttributeGroup?.Name}}</i>
                    </button>

                    <div [id]="option?.Id + attributeGroupMember.Id" class="collapse" [class.show]="true"
                      [attr.data-parent]="'#' + parent + option?.Id">
                      <div
                        *ngFor="let attribute of attributeGroupMember?.AttributeGroup?.ProductAttributes; let l = last">
                        <div class="pt-2 px-3 border-bottom border-gray" *ngIf="cartItem?.AttributeValue && cartItem?.AttributeValue[attribute.Field]">
                          <apt-output-field [record]="cartItem?.AttributeValue" [field]="attribute.Field"
                            [editable]="false" 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;
            .modal-content{
                height: 100vh;
            }
        }
    }
}

.width-fixed{
    min-width: 8rem;
    max-width: 8rem;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""