Browse Source

feat(admin-ui): Add metrics widget to dashboard

Michael Bromley 2 years ago
parent
commit
2acd00484d
32 changed files with 696 additions and 118 deletions
  1. 38 38
      packages/admin-ui/i18n-coverage.json
  2. 1 0
      packages/admin-ui/package.json
  3. 53 8
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  4. 0 13
      packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts
  5. 11 22
      packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts
  6. 1 0
      packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.html
  7. 72 0
      packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.scss
  8. 116 0
      packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.ts
  9. 186 0
      packages/admin-ui/src/lib/core/src/shared/components/chart/tooltip-plugin.ts
  10. 2 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  11. 2 1
      packages/admin-ui/src/lib/dashboard/src/components/dashboard/dashboard.component.html
  12. 2 1
      packages/admin-ui/src/lib/dashboard/src/dashboard.module.ts
  13. 6 10
      packages/admin-ui/src/lib/dashboard/src/default-widgets.ts
  14. 24 0
      packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.html
  15. 4 0
      packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.scss
  16. 71 0
      packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.ts
  17. 5 5
      packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.html
  18. 4 0
      packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.scss
  19. 18 4
      packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.ts
  20. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  21. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  22. 8 4
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  23. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  24. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  25. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/it.json
  26. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  27. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  28. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json
  29. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/ru.json
  30. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/uk.json
  31. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  32. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

+ 38 - 38
packages/admin-ui/i18n-coverage.json

@@ -1,71 +1,71 @@
 {
 {
-  "generatedOn": "2023-05-29T11:32:35.039Z",
-  "lastCommit": "f01a9ce747a897c4a07219c4713d3f3d8c6d405e",
+  "generatedOn": "2023-05-30T11:56:35.756Z",
+  "lastCommit": "be9b0a40cb64dbae58cdb03584d29c0dd1c8974a",
   "translationStatus": {
   "translationStatus": {
     "cs": {
     "cs": {
-      "tokenCount": 714,
-      "translatedCount": 550,
-      "percentage": 77
+      "tokenCount": 719,
+      "translatedCount": 549,
+      "percentage": 76
     },
     },
     "de": {
     "de": {
-      "tokenCount": 714,
-      "translatedCount": 533,
-      "percentage": 75
+      "tokenCount": 719,
+      "translatedCount": 532,
+      "percentage": 74
     },
     },
     "en": {
     "en": {
-      "tokenCount": 714,
-      "translatedCount": 711,
-      "percentage": 100
+      "tokenCount": 719,
+      "translatedCount": 712,
+      "percentage": 99
     },
     },
     "es": {
     "es": {
-      "tokenCount": 714,
-      "translatedCount": 575,
-      "percentage": 81
+      "tokenCount": 719,
+      "translatedCount": 574,
+      "percentage": 80
     },
     },
     "fr": {
     "fr": {
-      "tokenCount": 714,
-      "translatedCount": 570,
-      "percentage": 80
+      "tokenCount": 719,
+      "translatedCount": 569,
+      "percentage": 79
     },
     },
     "it": {
     "it": {
-      "tokenCount": 714,
-      "translatedCount": 574,
+      "tokenCount": 719,
+      "translatedCount": 573,
       "percentage": 80
       "percentage": 80
     },
     },
     "pl": {
     "pl": {
-      "tokenCount": 714,
-      "translatedCount": 384,
-      "percentage": 54
+      "tokenCount": 719,
+      "translatedCount": 383,
+      "percentage": 53
     },
     },
     "pt_BR": {
     "pt_BR": {
-      "tokenCount": 714,
-      "translatedCount": 548,
-      "percentage": 77
+      "tokenCount": 719,
+      "translatedCount": 547,
+      "percentage": 76
     },
     },
     "pt_PT": {
     "pt_PT": {
-      "tokenCount": 714,
-      "translatedCount": 583,
-      "percentage": 82
+      "tokenCount": 719,
+      "translatedCount": 582,
+      "percentage": 81
     },
     },
     "ru": {
     "ru": {
-      "tokenCount": 714,
-      "translatedCount": 573,
+      "tokenCount": 719,
+      "translatedCount": 572,
       "percentage": 80
       "percentage": 80
     },
     },
     "uk": {
     "uk": {
-      "tokenCount": 714,
-      "translatedCount": 573,
+      "tokenCount": 719,
+      "translatedCount": 572,
       "percentage": 80
       "percentage": 80
     },
     },
     "zh_Hans": {
     "zh_Hans": {
-      "tokenCount": 714,
-      "translatedCount": 519,
-      "percentage": 73
+      "tokenCount": 719,
+      "translatedCount": 518,
+      "percentage": 72
     },
     },
     "zh_Hant": {
     "zh_Hant": {
-      "tokenCount": 714,
-      "translatedCount": 364,
-      "percentage": 51
+      "tokenCount": 719,
+      "translatedCount": 363,
+      "percentage": 50
     }
     }
   }
   }
 }
 }

+ 1 - 0
packages/admin-ui/package.json

@@ -44,6 +44,7 @@
         "@webcomponents/custom-elements": "^1.5.1",
         "@webcomponents/custom-elements": "^1.5.1",
         "apollo-angular": "^4.2.1",
         "apollo-angular": "^4.2.1",
         "apollo-upload-client": "^17.0.0",
         "apollo-upload-client": "^17.0.0",
+        "chartist": "^1.3.0",
         "codejar": "^3.7.0",
         "codejar": "^3.7.0",
         "core-js": "^3.29.0",
         "core-js": "^3.29.0",
         "dayjs": "^1.10.4",
         "dayjs": "^1.10.4",

File diff suppressed because it is too large
+ 53 - 8
packages/admin-ui/src/lib/core/src/common/generated-types.ts


+ 0 - 13
packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts

@@ -480,19 +480,6 @@ export const TRANSITION_FULFILLMENT_TO_STATE = gql`
     ${ERROR_RESULT_FRAGMENT}
     ${ERROR_RESULT_FRAGMENT}
 `;
 `;
 
 
-export const GET_ORDER_SUMMARY = gql`
-    query GetOrderSummary($start: DateTime!, $end: DateTime!) {
-        orders(options: { filter: { orderPlacedAt: { between: { start: $start, end: $end } } } }) {
-            totalItems
-            items {
-                id
-                total
-                currencyCode
-            }
-        }
-    }
-`;
-
 export const MODIFY_ORDER = gql`
 export const MODIFY_ORDER = gql`
     mutation ModifyOrder($input: ModifyOrderInput!) {
     mutation ModifyOrder($input: ModifyOrderInput!) {
         modifyOrder(input: $input) {
         modifyOrder(input: $input) {

+ 11 - 22
packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts

@@ -13,19 +13,18 @@ import {
     DELETE_ORDER_NOTE,
     DELETE_ORDER_NOTE,
     DRAFT_ORDER_ELIGIBLE_SHIPPING_METHODS,
     DRAFT_ORDER_ELIGIBLE_SHIPPING_METHODS,
     GET_ORDER,
     GET_ORDER,
-    GET_ORDERS_LIST,
     GET_ORDER_HISTORY,
     GET_ORDER_HISTORY,
-    GET_ORDER_SUMMARY,
+    GET_ORDERS_LIST,
     MODIFY_ORDER,
     MODIFY_ORDER,
     REFUND_ORDER,
     REFUND_ORDER,
     REMOVE_COUPON_CODE_FROM_DRAFT_ORDER,
     REMOVE_COUPON_CODE_FROM_DRAFT_ORDER,
     REMOVE_DRAFT_ORDER_LINE,
     REMOVE_DRAFT_ORDER_LINE,
-    SETTLE_PAYMENT,
-    SETTLE_REFUND,
     SET_BILLING_ADDRESS_FOR_DRAFT_ORDER,
     SET_BILLING_ADDRESS_FOR_DRAFT_ORDER,
     SET_CUSTOMER_FOR_DRAFT_ORDER,
     SET_CUSTOMER_FOR_DRAFT_ORDER,
     SET_DRAFT_ORDER_SHIPPING_METHOD,
     SET_DRAFT_ORDER_SHIPPING_METHOD,
     SET_SHIPPING_ADDRESS_FOR_DRAFT_ORDER,
     SET_SHIPPING_ADDRESS_FOR_DRAFT_ORDER,
+    SETTLE_PAYMENT,
+    SETTLE_REFUND,
     TRANSITION_FULFILLMENT_TO_STATE,
     TRANSITION_FULFILLMENT_TO_STATE,
     TRANSITION_ORDER_TO_STATE,
     TRANSITION_ORDER_TO_STATE,
     TRANSITION_PAYMENT_TO_STATE,
     TRANSITION_PAYMENT_TO_STATE,
@@ -183,16 +182,6 @@ export class OrderDataService {
         });
         });
     }
     }
 
 
-    getOrderSummary(start: Date, end: Date) {
-        return this.baseDataService.query<
-            Codegen.GetOrderSummaryQuery,
-            Codegen.GetOrderSummaryQueryVariables
-        >(GET_ORDER_SUMMARY, {
-            start: start.toISOString(),
-            end: end.toISOString(),
-        });
-    }
-
     modifyOrder(input: Codegen.ModifyOrderInput) {
     modifyOrder(input: Codegen.ModifyOrderInput) {
         return this.baseDataService.mutate<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
         return this.baseDataService.mutate<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
             MODIFY_ORDER,
             MODIFY_ORDER,
@@ -214,17 +203,17 @@ export class OrderDataService {
     }
     }
 
 
     deleteDraftOrder(orderId: string) {
     deleteDraftOrder(orderId: string) {
-        return this.baseDataService.mutate<Codegen.DeleteDraftOrderMutation, Codegen.DeleteDraftOrderMutationVariables>(
-            DELETE_DRAFT_ORDER,
-            { orderId },
-        );
+        return this.baseDataService.mutate<
+            Codegen.DeleteDraftOrderMutation,
+            Codegen.DeleteDraftOrderMutationVariables
+        >(DELETE_DRAFT_ORDER, { orderId });
     }
     }
 
 
     addItemToDraftOrder(orderId: string, input: Codegen.AddItemToDraftOrderInput) {
     addItemToDraftOrder(orderId: string, input: Codegen.AddItemToDraftOrderInput) {
-        return this.baseDataService.mutate<Codegen.AddItemToDraftOrderMutation, Codegen.AddItemToDraftOrderMutationVariables>(
-            ADD_ITEM_TO_DRAFT_ORDER,
-            { orderId, input },
-        );
+        return this.baseDataService.mutate<
+            Codegen.AddItemToDraftOrderMutation,
+            Codegen.AddItemToDraftOrderMutationVariables
+        >(ADD_ITEM_TO_DRAFT_ORDER, { orderId, input });
     }
     }
 
 
     adjustDraftOrderLine(orderId: string, input: Codegen.AdjustDraftOrderLineInput) {
     adjustDraftOrderLine(orderId: string, input: Codegen.AdjustDraftOrderLineInput) {

+ 1 - 0
packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.html

@@ -0,0 +1 @@
+<div id="chart" style="height: 20vh; min-height: 300px;" #chartDiv></div>

+ 72 - 0
packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.scss

@@ -0,0 +1,72 @@
+::ng-deep {
+    $ct-line-width: 2px !default;
+    $ct-area-opacity: 0.5 !default;
+    $ct-series-colors: (
+        var(--color-primary-300),
+        var(--color-accent-300),
+        var(--color-primary-800),
+        var(--color-accent-800),
+        #453d3f,
+        #59922b,
+        #0544d3,
+        #6b0392,
+        #e6805e,
+        #dda458,
+        #eacf7d,
+        #86797d,
+        #b2c326,
+        #6188e2,
+        #a748ca
+    ) !default;
+
+    @import 'chartist/dist/index.scss';
+
+    .ct-series-a .ct-slice-pie,
+    .ct-series-a .ct-area {
+        fill: url(#gradient);
+    }
+
+    .ct-point {
+        opacity: 0;
+        transition: opacity 0.1s;
+    }
+
+    .chartist-tooltip {
+        position: absolute;
+        border-radius: var(--border-radius);
+        border: 1px solid var(--color-weight-100);
+        padding: 4px;
+        opacity: 0;
+        transition: opacity 0.1s, top 0.1s, left 0.1s;
+        font-size: 12px;
+        background: var(--color-component-bg-100);
+        box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05);
+        text-align: center;
+
+        &.tooltip-show {
+            opacity: 0.9;
+        }
+    }
+
+    .ct-tooltip-hover {
+        opacity: 1;
+    }
+
+    .tooltip-date {
+        color: var(--color-text-200);
+    }
+    .tooltip-value {
+        color: var(--color-primary-600);
+        font-size: 14px;
+        text-align: center;
+    }
+}
+
+:host {
+    display: block;
+    padding-bottom: 28px;
+}
+
+#chart {
+    position: relative;
+}

+ 116 - 0
packages/admin-ui/src/lib/core/src/shared/components/chart/chart.component.ts

@@ -0,0 +1,116 @@
+import {
+    ChangeDetectionStrategy,
+    Component,
+    ElementRef,
+    Input,
+    OnChanges,
+    OnDestroy,
+    OnInit,
+    SimpleChanges,
+    ViewChild,
+} from '@angular/core';
+import { easings, LineChart } from 'chartist';
+import { LineChartData } from 'chartist/dist/charts/LineChart/LineChart.types';
+import { tooltipPlugin } from './tooltip-plugin';
+
+export interface ChartFormatOptions {
+    formatValueAs: 'currency' | 'number';
+    currencyCode?: string;
+    locale?: string;
+}
+
+export interface ChartEntry {
+    label: string;
+    value: number;
+    formatOptions: ChartFormatOptions;
+}
+
+@Component({
+    selector: 'vdr-chart',
+    templateUrl: './chart.component.html',
+    styleUrls: ['./chart.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ChartComponent implements OnInit, OnChanges, OnDestroy {
+    @Input() entries: ChartEntry[] = [];
+    @ViewChild('chartDiv', { static: true }) private chartDivRef: ElementRef<HTMLDivElement>;
+    private chart: LineChart;
+
+    ngOnInit() {
+        this.chart = new LineChart(
+            this.chartDivRef.nativeElement,
+            this.entriesToLineChartData(this.entries ?? []),
+            {
+                low: 0,
+                showArea: true,
+                showLine: true,
+                showPoint: true,
+                fullWidth: true,
+                axisX: {
+                    showLabel: false,
+                    showGrid: false,
+                    offset: 1,
+                },
+                axisY: {
+                    showLabel: false,
+                    offset: 1,
+                },
+                plugins: [tooltipPlugin()],
+            },
+        );
+
+        this.chart.on('draw', data => {
+            if (data.type === 'line' || data.type === 'area') {
+                data.element.animate({
+                    d: {
+                        begin: 2000 * data.index,
+                        dur: 2000,
+                        from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(),
+                        to: data.path.clone().stringify(),
+                        easing: easings.easeOutQuint,
+                    },
+                });
+            }
+        });
+
+        // Create the gradient definition on created event (always after chart re-render)
+        this.chart.on('created', ctx => {
+            const defs = ctx.svg.elem('defs');
+            defs.elem('linearGradient', {
+                id: 'gradient',
+                x1: 0,
+                y1: 1,
+                x2: 0,
+                y2: 0,
+            })
+                .elem('stop', {
+                    offset: 0,
+                    'stop-color': 'var(--color-primary-400)',
+                    'stop-opacity': 0.3,
+                })
+                .parent()
+                ?.elem('stop', {
+                    offset: 1,
+                    'stop-color': 'var(--color-primary-500)',
+                });
+        });
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        if ('entries' in changes && this.chart) {
+            this.chart.update(this.entriesToLineChartData(this.entries ?? []));
+        }
+    }
+
+    ngOnDestroy() {
+        this.chart?.detach();
+    }
+
+    private entriesToLineChartData(entries: ChartEntry[]): LineChartData {
+        const labels = entries.map(({ label }) => label);
+        const series = [
+            entries.map(({ label, value, formatOptions }) => ({ meta: { label, formatOptions }, value })),
+        ];
+        return { labels, series };
+    }
+}

+ 186 - 0
packages/admin-ui/src/lib/core/src/shared/components/chart/tooltip-plugin.ts

@@ -0,0 +1,186 @@
+/**
+ * Based on https://github.com/tmmdata/chartist-plugin-tooltip/blob/master/src/scripts/chartist-plugin-tooltip.js
+ *
+ */
+/* global Chartist */
+
+import { LineChart, PieChart } from 'chartist';
+import { DrawEvent } from 'chartist/dist/core/types';
+import { ChartFormatOptions } from './chart.component';
+
+const defaultOptions = {
+    currency: undefined,
+    currencyFormatCallback: undefined,
+    tooltipOffset: {
+        x: 0,
+        y: -20,
+    },
+    anchorToPoint: false,
+    appendToBody: false,
+    class: undefined,
+    pointClass: 'ct-point',
+};
+
+export function tooltipPlugin(userOptions?: any) {
+    return function tooltip(chart: LineChart) {
+        const options = {
+            ...defaultOptions,
+            ...userOptions,
+        };
+        const tooltipSelector = options.pointClass;
+
+        const $chart = (chart as any).container as HTMLDivElement;
+        let $toolTip = $chart.querySelector('.chartist-tooltip') as HTMLDivElement;
+        if (!$toolTip) {
+            $toolTip = document.createElement('div');
+            $toolTip.className = !options.class ? 'chartist-tooltip' : 'chartist-tooltip ' + options.class;
+            if (!options.appendToBody) {
+                $chart.appendChild($toolTip);
+            } else {
+                document.body.appendChild($toolTip);
+            }
+        }
+        let height = $toolTip.offsetHeight;
+        let width = $toolTip.offsetWidth;
+        const points: Array<{
+            event: DrawEvent;
+            x: number;
+        }> = [];
+
+        function getClosestPoint(mouseX: number): DrawEvent {
+            let closestElement: DrawEvent | null = null;
+            let closestDistance = Infinity;
+
+            // Iterate through the points array to find the closest element
+            for (const point of points) {
+                const elementX = point.x;
+                const distance = calculateDistance(mouseX, elementX);
+
+                if (distance < closestDistance) {
+                    closestElement = point.event;
+                    closestDistance = distance;
+                }
+            }
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            return closestElement!;
+        }
+
+        chart.on('draw', data => {
+            if (data.type === 'point') {
+                const element = data.element;
+                points[data.index] = {
+                    event: data,
+                    x: data.element.getNode().getBoundingClientRect().x,
+                };
+                console.log(`drawpoint`);
+            }
+        });
+
+        hide($toolTip);
+
+        function on(event, selector, callback) {
+            $chart.addEventListener(
+                event,
+                function (e) {
+                    if (!selector || hasClass(e.target, selector)) {
+                        callback(e);
+                    }
+                },
+                { passive: true },
+            );
+        }
+
+        on('mousemove', undefined, (event: MouseEvent) => {
+            const closestPoint = getClosestPoint(event.clientX);
+            points.forEach(point => point.event.element.removeClass('ct-tooltip-hover'));
+            closestPoint.element.addClass('ct-tooltip-hover');
+
+            const $point = closestPoint.element.getNode() as HTMLElement;
+
+            const seriesName = 'ct:series-name';
+            const meta: {
+                label: string;
+                formatOptions: ChartFormatOptions;
+            } = closestPoint.meta;
+            const value = $point.getAttribute('ct:value');
+
+            const dateFormatter = new Intl.DateTimeFormat(meta.formatOptions.locale);
+            const formattedValue =
+                meta.formatOptions.formatValueAs === 'currency'
+                    ? new Intl.NumberFormat(meta.formatOptions.locale, {
+                          style: 'currency',
+                          currency: meta.formatOptions.currencyCode,
+                          minimumFractionDigits: 2,
+                      }).format(+(value ?? 0) / 100)
+                    : new Intl.NumberFormat(meta.formatOptions.locale).format(+(value ?? 0));
+
+            const tooltipText = `
+            <div class="tooltip-date">${dateFormatter.format(new Date(meta.label))}</div>
+            <div class="tooltip-value">${formattedValue}</div>
+           `;
+
+            $toolTip.innerHTML = tooltipText;
+            setPosition($point);
+            show($toolTip);
+
+            // Remember height and width to avoid wrong position in IE
+            height = $toolTip.offsetHeight;
+            width = $toolTip.offsetWidth;
+        });
+
+        on('mouseleave', undefined, () => {
+            hide($toolTip);
+        });
+
+        function setPosition(element: HTMLElement) {
+            height = height || $toolTip.offsetHeight;
+            width = width || $toolTip.offsetWidth;
+            const { x: elX, y: elY, width: elWidth, height: elHeight } = element.getBoundingClientRect();
+            const offsetX = -width / 2 + options.tooltipOffset.x;
+            const offsetY = -height + options.tooltipOffset.y;
+            let anchorX;
+            let anchorY;
+
+            if (!options.appendToBody) {
+                const box = $chart.getBoundingClientRect();
+                const left = elX - box.left - window.pageXOffset;
+                const top = elY - box.top - window.pageYOffset;
+
+                $toolTip.style.top = (anchorY || top) + offsetY + 'px';
+                $toolTip.style.left = (anchorX || left) + offsetX + 'px';
+            } else {
+                $toolTip.style.top = elY + offsetY + 'px';
+                $toolTip.style.left = elX + offsetX + 'px';
+            }
+        }
+    };
+}
+
+function show(element) {
+    if (!hasClass(element, 'tooltip-show')) {
+        element.className = element.className + ' tooltip-show';
+    }
+}
+
+function hide(element) {
+    const regex = new RegExp('tooltip-show' + '\\s*', 'gi');
+    element.className = element.className.replace(regex, '').trim();
+}
+
+function hasClass(element, className) {
+    return (' ' + element.getAttribute('class') + ' ').indexOf(' ' + className + ' ') > -1;
+}
+
+function next(element, className) {
+    do {
+        element = element.nextSibling;
+    } while (element && !hasClass(element, className));
+    return element;
+}
+
+function text(element) {
+    return element.innerText || element.textContent;
+}
+function calculateDistance(x1, x2) {
+    return Math.abs(x2 - x1);
+}

+ 2 - 0
packages/admin-ui/src/lib/core/src/shared/shared.module.ts

@@ -166,6 +166,7 @@ import { TimeAgoPipe } from './pipes/time-ago.pipe';
 import { CanDeactivateDetailGuard } from './providers/routing/can-deactivate-detail-guard';
 import { CanDeactivateDetailGuard } from './providers/routing/can-deactivate-detail-guard';
 import { CardComponent, CardControlsDirective } from './components/card/card.component';
 import { CardComponent, CardControlsDirective } from './components/card/card.component';
 import { ZoneSelectorComponent } from './components/zone-selector/zone-selector.component';
 import { ZoneSelectorComponent } from './components/zone-selector/zone-selector.component';
+import { ChartComponent } from './components/chart/chart.component';
 
 
 const IMPORTS = [
 const IMPORTS = [
     ClarityModule,
     ClarityModule,
@@ -304,6 +305,7 @@ const DECLARATIONS = [
     CardComponent,
     CardComponent,
     CardControlsDirective,
     CardControlsDirective,
     ZoneSelectorComponent,
     ZoneSelectorComponent,
+    ChartComponent,
 ];
 ];
 
 
 const DYNAMIC_FORM_INPUTS = [
 const DYNAMIC_FORM_INPUTS = [

+ 2 - 1
packages/admin-ui/src/lib/dashboard/src/components/dashboard/dashboard.component.html

@@ -1,9 +1,10 @@
 <vdr-page-block>
 <vdr-page-block>
-    <div class="widget-header">
+    <div class="widget-header mb-1">
         <vdr-dropdown>
         <vdr-dropdown>
             <button class="btn btn-secondary btn-sm" vdrDropdownTrigger>
             <button class="btn btn-secondary btn-sm" vdrDropdownTrigger>
                 <clr-icon shape="plus"></clr-icon>
                 <clr-icon shape="plus"></clr-icon>
                 {{ 'dashboard.add-widget' | translate }}
                 {{ 'dashboard.add-widget' | translate }}
+                <clr-icon shape="ellipsis-vertical"></clr-icon>
             </button>
             </button>
             <vdr-dropdown-menu vdrPosition="bottom-right">
             <vdr-dropdown-menu vdrPosition="bottom-right">
                 <button
                 <button

+ 2 - 1
packages/admin-ui/src/lib/dashboard/src/dashboard.module.ts

@@ -6,10 +6,11 @@ import { DashboardWidgetComponent } from './components/dashboard-widget/dashboar
 import { DashboardComponent } from './components/dashboard/dashboard.component';
 import { DashboardComponent } from './components/dashboard/dashboard.component';
 import { dashboardRoutes } from './dashboard.routes';
 import { dashboardRoutes } from './dashboard.routes';
 import { DEFAULT_DASHBOARD_WIDGET_LAYOUT, DEFAULT_WIDGETS } from './default-widgets';
 import { DEFAULT_DASHBOARD_WIDGET_LAYOUT, DEFAULT_WIDGETS } from './default-widgets';
+import { OrderChartWidgetComponent } from './widgets/order-chart-widget/order-chart-widget.component';
 
 
 @NgModule({
 @NgModule({
     imports: [SharedModule, RouterModule.forChild(dashboardRoutes)],
     imports: [SharedModule, RouterModule.forChild(dashboardRoutes)],
-    declarations: [DashboardComponent, DashboardWidgetComponent],
+    declarations: [DashboardComponent, DashboardWidgetComponent, OrderChartWidgetComponent],
 })
 })
 export class DashboardModule {
 export class DashboardModule {
     constructor(dashboardWidgetService: DashboardWidgetService) {
     constructor(dashboardWidgetService: DashboardWidgetService) {

+ 6 - 10
packages/admin-ui/src/lib/dashboard/src/default-widgets.ts

@@ -1,16 +1,10 @@
-import { APP_INITIALIZER, FactoryProvider } from '@angular/core';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import {
-    DashboardWidgetConfig,
-    DashboardWidgetService,
-    Permission,
-    WidgetLayoutDefinition,
-} from '@vendure/admin-ui/core';
+import { DashboardWidgetConfig, Permission, WidgetLayoutDefinition } from '@vendure/admin-ui/core';
 
 
 import { LatestOrdersWidgetComponent } from './widgets/latest-orders-widget/latest-orders-widget.component';
 import { LatestOrdersWidgetComponent } from './widgets/latest-orders-widget/latest-orders-widget.component';
+import { OrderChartWidgetComponent } from './widgets/order-chart-widget/order-chart-widget.component';
 import { OrderSummaryWidgetComponent } from './widgets/order-summary-widget/order-summary-widget.component';
 import { OrderSummaryWidgetComponent } from './widgets/order-summary-widget/order-summary-widget.component';
 import { TestWidgetComponent } from './widgets/test-widget/test-widget.component';
 import { TestWidgetComponent } from './widgets/test-widget/test-widget.component';
-import { WelcomeWidgetComponent } from './widgets/welcome-widget/welcome-widget.component';
 
 
 export const DEFAULT_DASHBOARD_WIDGET_LAYOUT: WidgetLayoutDefinition = [
 export const DEFAULT_DASHBOARD_WIDGET_LAYOUT: WidgetLayoutDefinition = [
     { id: 'welcome', width: 12 },
     { id: 'welcome', width: 12 },
@@ -19,8 +13,10 @@ export const DEFAULT_DASHBOARD_WIDGET_LAYOUT: WidgetLayoutDefinition = [
 ];
 ];
 
 
 export const DEFAULT_WIDGETS: { [id: string]: DashboardWidgetConfig } = {
 export const DEFAULT_WIDGETS: { [id: string]: DashboardWidgetConfig } = {
-    welcome: {
-        loadComponent: () => WelcomeWidgetComponent,
+    orderChart: {
+        title: _('dashboard.metrics'),
+        supportedWidths: [6, 8, 12],
+        loadComponent: () => OrderChartWidgetComponent,
     },
     },
     orderSummary: {
     orderSummary: {
         title: _('dashboard.orders-summary'),
         title: _('dashboard.orders-summary'),

+ 24 - 0
packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.html

@@ -0,0 +1,24 @@
+<vdr-chart [entries]="metrics$ | async" />
+<div class="flex" *ngIf="metricType$ | async as activeMetricType">
+    <button
+        class="button-small"
+        (click)="metricType$.next(MetricType.OrderTotal)"
+        [class.active]="activeMetricType === MetricType.OrderTotal"
+    >
+        {{ 'dashboard.metric-order-total-value' | translate }}
+    </button>
+    <button
+        class="ml-1 button-small"
+        (click)="metricType$.next(MetricType.OrderCount)"
+        [class.active]="activeMetricType === MetricType.OrderCount"
+    >
+        {{ 'dashboard.metric-number-of-orders' | translate }}
+    </button>
+    <button
+        class="ml-1 button-small"
+        (click)="metricType$.next(MetricType.AverageOrderValue)"
+        [class.active]="activeMetricType === MetricType.AverageOrderValue"
+    >
+        {{ 'dashboard.metric-average-order-value' | translate }}
+    </button>
+</div>

+ 4 - 0
packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.scss

@@ -0,0 +1,4 @@
+.button-small.active {
+    background-color: var(--color-primary-200);
+    color: var(--color-primary-900);
+}

+ 71 - 0
packages/admin-ui/src/lib/dashboard/src/widgets/order-chart-widget/order-chart-widget.component.ts

@@ -0,0 +1,71 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { CurrencyCode, DataService, GetOrderChartDataDocument, MetricType } from '@vendure/admin-ui/core';
+import { pick } from '@vendure/common/lib/pick';
+import { gql } from 'apollo-angular';
+import { combineLatest, BehaviorSubject, Observable, switchMap } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { ChartEntry, ChartFormatOptions } from '../../../../core/src/shared/components/chart/chart.component';
+
+export const GET_ORDER_CHART_DATA = gql`
+    query GetOrderChartData($refresh: Boolean, $types: [MetricType!]!) {
+        metricSummary(input: { interval: Daily, types: $types, refresh: $refresh }) {
+            interval
+            type
+            entries {
+                label
+                value
+            }
+        }
+    }
+`;
+
+@Component({
+    selector: 'vdr-order-chart-widget',
+    templateUrl: './order-chart-widget.component.html',
+    styleUrls: ['./order-chart-widget.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class OrderChartWidgetComponent implements OnInit {
+    constructor(private dataService: DataService) {}
+    metrics$: Observable<ChartEntry[]>;
+    refresh$ = new BehaviorSubject(false);
+    metricType$ = new BehaviorSubject(MetricType.OrderTotal);
+    MetricType = MetricType;
+
+    ngOnInit() {
+        const currencyCode$ = this.dataService.settings
+            .getActiveChannel()
+            .refetchOnChannelChange()
+            .mapStream(data => data.activeChannel.currencyCode || undefined);
+        const uiState$ = this.dataService.client.uiState().mapStream(data => data.uiState);
+
+        this.metrics$ = combineLatest(this.refresh$, this.metricType$, currencyCode$, uiState$).pipe(
+            switchMap(([refresh, metricType, currencyCode, uiState]) =>
+                this.dataService
+                    .query(GetOrderChartDataDocument, {
+                        types: [metricType],
+                        refresh,
+                    })
+                    .mapSingle(data => data.metricSummary)
+                    .pipe(
+                        map(metrics => {
+                            const formatValueAs: 'currency' | 'number' =
+                                metricType === MetricType.OrderCount ? 'number' : 'currency';
+                            const locale = `${uiState.language}-${uiState.locale}`;
+
+                            const formatOptions: ChartFormatOptions = {
+                                formatValueAs,
+                                currencyCode,
+                                locale,
+                            };
+                            return (
+                                metrics
+                                    .find(m => m.type === metricType)
+                                    ?.entries.map(entry => ({ ...entry, formatOptions })) ?? []
+                            );
+                        }),
+                    ),
+            ),
+        );
+    }
+}

+ 5 - 5
packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.html

@@ -11,17 +11,17 @@
     </div>
     </div>
 </div>
 </div>
 <div class="footer">
 <div class="footer">
-    <div class="btn-group btn-outline-primary btn-sm" *ngIf="selection$ | async as selection">
-        <button class="btn" [class.btn-primary]="selection.date === today" (click)="selection$.next({timeframe: 'day', date: today})">
+    <div class="flex" *ngIf="selection$ | async as selection">
+        <button class="button-small" [class.active]="selection.date === today" (click)="selection$.next({timeframe: 'day', date: today})">
             {{ 'dashboard.today' | translate }}
             {{ 'dashboard.today' | translate }}
         </button>
         </button>
-        <button class="btn" [class.btn-primary]="selection.date === yesterday" (click)="selection$.next({timeframe: 'day', date: yesterday})">
+        <button class="ml-1 button-small" [class.active]="selection.date === yesterday" (click)="selection$.next({timeframe: 'day', date: yesterday})">
             {{ 'dashboard.yesterday' | translate }}
             {{ 'dashboard.yesterday' | translate }}
         </button>
         </button>
-        <button class="btn" [class.btn-primary]="selection.timeframe === 'week'" (click)="selection$.next({timeframe: 'week'})">
+        <button class="ml-1 button-small" [class.active]="selection.timeframe === 'week'" (click)="selection$.next({timeframe: 'week'})">
             {{ 'dashboard.thisWeek' | translate }}
             {{ 'dashboard.thisWeek' | translate }}
         </button>
         </button>
-        <button class="btn" [class.btn-primary]="selection.timeframe === 'month'" (click)="selection$.next({timeframe: 'month'})">
+        <button class="ml-1 button-small" [class.active]="selection.timeframe === 'month'" (click)="selection$.next({timeframe: 'month'})">
             {{ 'dashboard.thisMonth' | translate }}
             {{ 'dashboard.thisMonth' | translate }}
         </button>
         </button>
     </div>
     </div>

+ 4 - 0
packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.scss

@@ -23,3 +23,7 @@
     flex-direction: column;
     flex-direction: column;
     justify-content: space-between;
     justify-content: space-between;
 }
 }
+.button-small.active {
+    background-color: var(--color-primary-200);
+    color: var(--color-primary-900);
+}

+ 18 - 4
packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.ts

@@ -1,11 +1,25 @@
 import { ChangeDetectionStrategy, Component, NgModule, OnInit } from '@angular/core';
 import { ChangeDetectionStrategy, Component, NgModule, OnInit } from '@angular/core';
-import { CoreModule, DataService } from '@vendure/admin-ui/core';
+import { CoreModule, DataService, GetOrderSummaryDocument } from '@vendure/admin-ui/core';
+import { gql } from 'apollo-angular';
 import dayjs from 'dayjs';
 import dayjs from 'dayjs';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { distinctUntilChanged, map, shareReplay, switchMap } from 'rxjs/operators';
 import { distinctUntilChanged, map, shareReplay, switchMap } from 'rxjs/operators';
 
 
 export type Timeframe = 'day' | 'week' | 'month';
 export type Timeframe = 'day' | 'week' | 'month';
 
 
+export const GET_ORDER_SUMMARY = gql`
+    query GetOrderSummary($start: DateTime!, $end: DateTime!) {
+        orders(options: { filter: { orderPlacedAt: { between: { start: $start, end: $end } } } }) {
+            totalItems
+            items {
+                id
+                totalWithTax
+                currencyCode
+            }
+        }
+    }
+`;
+
 @Component({
 @Component({
     selector: 'vdr-order-summary-widget',
     selector: 'vdr-order-summary-widget',
     templateUrl: './order-summary-widget.component.html',
     templateUrl: './order-summary-widget.component.html',
@@ -37,8 +51,8 @@ export class OrderSummaryWidgetComponent implements OnInit {
         );
         );
         const orderSummary$ = this.dateRange$.pipe(
         const orderSummary$ = this.dateRange$.pipe(
             switchMap(({ start, end }) =>
             switchMap(({ start, end }) =>
-                this.dataService.order
-                    .getOrderSummary(start, end)
+                this.dataService
+                    .query(GetOrderSummaryDocument, { start: start.toISOString(), end: end.toISOString() })
                     .refetchOnChannelChange()
                     .refetchOnChannelChange()
                     .mapStream(data => data.orders),
                     .mapStream(data => data.orders),
             ),
             ),
@@ -46,7 +60,7 @@ export class OrderSummaryWidgetComponent implements OnInit {
         );
         );
         this.totalOrderCount$ = orderSummary$.pipe(map(res => res.totalItems));
         this.totalOrderCount$ = orderSummary$.pipe(map(res => res.totalItems));
         this.totalOrderValue$ = orderSummary$.pipe(
         this.totalOrderValue$ = orderSummary$.pipe(
-            map(res => res.items.reduce((total, order) => total + order.total, 0) / 100),
+            map(res => res.items.reduce((total, order) => total + order.totalWithTax, 0) / 100),
         );
         );
         this.currencyCode$ = this.dataService.settings
         this.currencyCode$ = this.dataService.settings
             .getActiveChannel()
             .getActiveChannel()

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Odebrat variantu z kanálu",
     "remove-product-variant-from-channel": "Odebrat variantu z kanálu",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "",
     "search-asset-name-or-tag": "",
     "search-for-term": "Hledat výraz",
     "search-for-term": "Hledat výraz",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Více...",
     "more": "Více...",
     "name": "jméno",
     "name": "jméno",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Žádné výsledky",
     "no-results": "Žádné výsledky",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Aktualizováno: { entity }",
     "notify-update-success": "Aktualizováno: { entity }",
     "notify-updated-tags-success": "",
     "notify-updated-tags-success": "",
     "okay": "",
     "okay": "",
-    "open": "Otevřít",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Přidat widget",
     "add-widget": "Přidat widget",
     "latest-orders": "Poslední objednávky",
     "latest-orders": "Poslední objednávky",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Souhrn objednávek",
     "orders-summary": "Souhrn objednávek",
     "remove-widget": "Odebrat widget",
     "remove-widget": "Odebrat widget",
     "thisMonth": "Tento měsíc",
     "thisMonth": "Tento měsíc",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "",
     "remove-product-variant-from-channel": "",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "",
     "search-asset-name-or-tag": "",
     "search-for-term": "Suche nach Begriff",
     "search-for-term": "Suche nach Begriff",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Mehr...",
     "more": "Mehr...",
     "name": "Name",
     "name": "Name",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Keine Ergebnisse",
     "no-results": "Keine Ergebnisse",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "{ entity } aktualisiert",
     "notify-update-success": "{ entity } aktualisiert",
     "notify-updated-tags-success": "Tags aktualisiert",
     "notify-updated-tags-success": "Tags aktualisiert",
     "okay": "",
     "okay": "",
-    "open": "Öffnen",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Widget hinzufügen",
     "add-widget": "Widget hinzufügen",
     "latest-orders": "Letzte Bestellungen",
     "latest-orders": "Letzte Bestellungen",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Bestellungszusammenfassung",
     "orders-summary": "Bestellungszusammenfassung",
     "remove-widget": "Widget entfernen",
     "remove-widget": "Widget entfernen",
     "thisMonth": "Dieser Monat",
     "thisMonth": "Dieser Monat",

+ 8 - 4
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -158,8 +158,8 @@
     "remove-product-variant-from-channel": "Remove product variant from channel",
     "remove-product-variant-from-channel": "Remove product variant from channel",
     "reorder-collection": "Re-order collection",
     "reorder-collection": "Re-order collection",
     "root-collection": "Root collection",
     "root-collection": "Root collection",
-    "running-search-index-updates": "Running {count, plural, one {1 update} other {{count} updates}} to search index",
     "run-pending-search-index-updates": "Search index: run {count, plural, one {1 pending update} other {{count} pending updates}}",
     "run-pending-search-index-updates": "Search index: run {count, plural, one {1 pending update} other {{count} pending updates}}",
+    "running-search-index-updates": "Running {count, plural, one {1 update} other {{count} updates}} to search index",
     "search-asset-name-or-tag": "Search by asset name or tags",
     "search-asset-name-or-tag": "Search by asset name or tags",
     "search-for-term": "Search for term",
     "search-for-term": "Search for term",
     "search-product-name-or-code": "Search by product name or code",
     "search-product-name-or-code": "Search by product name or code",
@@ -254,6 +254,7 @@
     "medium-date": "Medium date",
     "medium-date": "Medium date",
     "more": "More...",
     "more": "More...",
     "name": "Name",
     "name": "Name",
+    "no-alerts": "No alerts",
     "no-bulk-actions-available": "No bulk actions available",
     "no-bulk-actions-available": "No bulk actions available",
     "no-results": "No results",
     "no-results": "No results",
     "not-applicable": "Not applicable",
     "not-applicable": "Not applicable",
@@ -270,7 +271,6 @@
     "notify-update-success": "Updated { entity }",
     "notify-update-success": "Updated { entity }",
     "notify-updated-tags-success": "Successfully updated tags",
     "notify-updated-tags-success": "Successfully updated tags",
     "okay": "Okay",
     "okay": "Okay",
-    "open": "Open",
     "operator-contains": "contains",
     "operator-contains": "contains",
     "operator-eq": "equals",
     "operator-eq": "equals",
     "operator-gt": "greater than",
     "operator-gt": "greater than",
@@ -395,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Add widget",
     "add-widget": "Add widget",
     "latest-orders": "Latest orders",
     "latest-orders": "Latest orders",
+    "metric-average-order-value": "Average order value",
+    "metric-number-of-orders": "Number of orders",
+    "metric-order-total-value": "Order total value",
+    "metrics": "Metrics",
     "orders-summary": "Orders summary",
     "orders-summary": "Orders summary",
     "remove-widget": "Remove widget",
     "remove-widget": "Remove widget",
     "thisMonth": "This month",
     "thisMonth": "This month",
@@ -573,11 +577,11 @@
     "note-is-private": "Note is private",
     "note-is-private": "Note is private",
     "note-only-visible-to-administrators": "Visible to admins only",
     "note-only-visible-to-administrators": "Visible to admins only",
     "note-visible-to-customer": "Visible to admins and customer",
     "note-visible-to-customer": "Visible to admins and customer",
-    "order": "",
+    "order": "Order",
     "order-history": "Order history",
     "order-history": "Order history",
     "order-state-diagram": "Order state diagram",
     "order-state-diagram": "Order state diagram",
     "order-type": "Order type",
     "order-type": "Order type",
-    "orders": "",
+    "orders": "Orders",
     "payment": "Payment",
     "payment": "Payment",
     "payment-amount": "Payment amount",
     "payment-amount": "Payment amount",
     "payment-metadata": "Payment metadata",
     "payment-metadata": "Payment metadata",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Eliminar variante de producto del canal de ventas ",
     "remove-product-variant-from-channel": "Eliminar variante de producto del canal de ventas ",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "Buscar por nombre de recurso o faceta",
     "search-asset-name-or-tag": "Buscar por nombre de recurso o faceta",
     "search-for-term": "Buscar por término",
     "search-for-term": "Buscar por término",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Más...",
     "more": "Más...",
     "name": "Nombre",
     "name": "Nombre",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Sin resultados",
     "no-results": "Sin resultados",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Actualizado { entity }",
     "notify-update-success": "Actualizado { entity }",
     "notify-updated-tags-success": "Etiquetas actualizadas con éxito",
     "notify-updated-tags-success": "Etiquetas actualizadas con éxito",
     "okay": "",
     "okay": "",
-    "open": "Abrir",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Añadir widget",
     "add-widget": "Añadir widget",
     "latest-orders": "Últimos pedidos",
     "latest-orders": "Últimos pedidos",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Resumen de pedidos",
     "orders-summary": "Resumen de pedidos",
     "remove-widget": "Eliminar widget",
     "remove-widget": "Eliminar widget",
     "thisMonth": "Este mes",
     "thisMonth": "Este mes",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Retirer la variante du produit du canal",
     "remove-product-variant-from-channel": "Retirer la variante du produit du canal",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "Rechercher par nom de fichier ou mot-clé",
     "search-asset-name-or-tag": "Rechercher par nom de fichier ou mot-clé",
     "search-for-term": "Chercher le terme",
     "search-for-term": "Chercher le terme",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Plus...",
     "more": "Plus...",
     "name": "Nom",
     "name": "Nom",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Aucun resultat",
     "no-results": "Aucun resultat",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "{ entity } mis à jour",
     "notify-update-success": "{ entity } mis à jour",
     "notify-updated-tags-success": "Mots-clés mis à jour avec succès",
     "notify-updated-tags-success": "Mots-clés mis à jour avec succès",
     "okay": "",
     "okay": "",
-    "open": "Ouvert",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Ajouter widget",
     "add-widget": "Ajouter widget",
     "latest-orders": "Dernières commandes",
     "latest-orders": "Dernières commandes",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Résumé des commandes",
     "orders-summary": "Résumé des commandes",
     "remove-widget": "Supperimer widget",
     "remove-widget": "Supperimer widget",
     "thisMonth": "Ce mois-ci",
     "thisMonth": "Ce mois-ci",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/it.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Rimuovi variante dal canale",
     "remove-product-variant-from-channel": "Rimuovi variante dal canale",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "Cerca per nome o tag del media",
     "search-asset-name-or-tag": "Cerca per nome o tag del media",
     "search-for-term": "Ricerca termine",
     "search-for-term": "Ricerca termine",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Altri...",
     "more": "Altri...",
     "name": "Nome",
     "name": "Nome",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Nessun risultato",
     "no-results": "Nessun risultato",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Aggiornato { entity }",
     "notify-update-success": "Aggiornato { entity }",
     "notify-updated-tags-success": "Tags aggiornati con successo",
     "notify-updated-tags-success": "Tags aggiornati con successo",
     "okay": "",
     "okay": "",
-    "open": "Apri",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Aggiungi widget",
     "add-widget": "Aggiungi widget",
     "latest-orders": "Ultimi ordini",
     "latest-orders": "Ultimi ordini",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Riepilogo ordini",
     "orders-summary": "Riepilogo ordini",
     "remove-widget": "Rimuovi widget",
     "remove-widget": "Rimuovi widget",
     "thisMonth": "Questo mese",
     "thisMonth": "Questo mese",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "",
     "remove-product-variant-from-channel": "",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "",
     "search-asset-name-or-tag": "",
     "search-for-term": "Szukaj frazy",
     "search-for-term": "Szukaj frazy",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Więcej...",
     "more": "Więcej...",
     "name": "Nazwa",
     "name": "Nazwa",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Brak wyników",
     "no-results": "Brak wyników",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Zaktualizowano { entity }",
     "notify-update-success": "Zaktualizowano { entity }",
     "notify-updated-tags-success": "",
     "notify-updated-tags-success": "",
     "okay": "",
     "okay": "",
-    "open": "Otwórz",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "",
     "add-widget": "",
     "latest-orders": "",
     "latest-orders": "",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "",
     "orders-summary": "",
     "remove-widget": "",
     "remove-widget": "",
     "thisMonth": "",
     "thisMonth": "",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "",
     "remove-product-variant-from-channel": "",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "",
     "search-asset-name-or-tag": "",
     "search-for-term": "Pesquisar termo",
     "search-for-term": "Pesquisar termo",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Mais...",
     "more": "Mais...",
     "name": "Nome",
     "name": "Nome",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Sem resultados",
     "no-results": "Sem resultados",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Atualizado { entity }",
     "notify-update-success": "Atualizado { entity }",
     "notify-updated-tags-success": "",
     "notify-updated-tags-success": "",
     "okay": "",
     "okay": "",
-    "open": "Aberto",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Adicionar widget",
     "add-widget": "Adicionar widget",
     "latest-orders": "Últimos pedidos",
     "latest-orders": "Últimos pedidos",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Resumo de pedidos",
     "orders-summary": "Resumo de pedidos",
     "remove-widget": "Remover widget",
     "remove-widget": "Remover widget",
     "thisMonth": "Este mês",
     "thisMonth": "Este mês",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Remover variante do canal?",
     "remove-product-variant-from-channel": "Remover variante do canal?",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "A executar {count, plural, one {1 actualização} other {{count} actualizações}}",
     "running-search-index-updates": "A executar {count, plural, one {1 actualização} other {{count} actualizações}}",
     "search-asset-name-or-tag": "Pesquisar pelo nome ou tag",
     "search-asset-name-or-tag": "Pesquisar pelo nome ou tag",
     "search-for-term": "Pesquisar termo",
     "search-for-term": "Pesquisar termo",
@@ -253,6 +254,7 @@
     "medium-date": "Data média",
     "medium-date": "Data média",
     "more": "Mais...",
     "more": "Mais...",
     "name": "Nome",
     "name": "Nome",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Nenhum resultado encontrado",
     "no-results": "Nenhum resultado encontrado",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Entidade ({ entity }) actualizada com sucesso",
     "notify-update-success": "Entidade ({ entity }) actualizada com sucesso",
     "notify-updated-tags-success": "Tags actualizadas com sucesso",
     "notify-updated-tags-success": "Tags actualizadas com sucesso",
     "okay": "",
     "okay": "",
-    "open": "Visualizar",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Adicionar widget",
     "add-widget": "Adicionar widget",
     "latest-orders": "Últimas encomendas",
     "latest-orders": "Últimas encomendas",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Resumo de encomendas",
     "orders-summary": "Resumo de encomendas",
     "remove-widget": "Remover widget",
     "remove-widget": "Remover widget",
     "thisMonth": "Este mês",
     "thisMonth": "Este mês",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/ru.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Удалить вариант товара из канала",
     "remove-product-variant-from-channel": "Удалить вариант товара из канала",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "Поиск по названию медиа-объект или тегам",
     "search-asset-name-or-tag": "Поиск по названию медиа-объект или тегам",
     "search-for-term": "Искать по фразе",
     "search-for-term": "Искать по фразе",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Больше...",
     "more": "Больше...",
     "name": "Имя",
     "name": "Имя",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Нет результатов",
     "no-results": "Нет результатов",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Обновлено { entity }",
     "notify-update-success": "Обновлено { entity }",
     "notify-updated-tags-success": "Успешно обновлены теги",
     "notify-updated-tags-success": "Успешно обновлены теги",
     "okay": "",
     "okay": "",
-    "open": "Открыть",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Добавить виджет",
     "add-widget": "Добавить виджет",
     "latest-orders": "Последние заказы",
     "latest-orders": "Последние заказы",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Сводка заказов",
     "orders-summary": "Сводка заказов",
     "remove-widget": "Удалить виджет",
     "remove-widget": "Удалить виджет",
     "thisMonth": "Этот месяц",
     "thisMonth": "Этот месяц",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/uk.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "Видалити варіант товару з каналу",
     "remove-product-variant-from-channel": "Видалити варіант товару з каналу",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "Пошук за назвою медіа-об'єкта або тегами",
     "search-asset-name-or-tag": "Пошук за назвою медіа-об'єкта або тегами",
     "search-for-term": "Шукати по фразі",
     "search-for-term": "Шукати по фразі",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "Більше...",
     "more": "Більше...",
     "name": "Ім'я",
     "name": "Ім'я",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "Немає результатів",
     "no-results": "Немає результатів",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "Оновлено { entity }",
     "notify-update-success": "Оновлено { entity }",
     "notify-updated-tags-success": "Успішно оновлені теги",
     "notify-updated-tags-success": "Успішно оновлені теги",
     "okay": "",
     "okay": "",
-    "open": "Відкрити",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "Додати віджет",
     "add-widget": "Додати віджет",
     "latest-orders": "Останні замовлення",
     "latest-orders": "Останні замовлення",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "Зведення замовлень",
     "orders-summary": "Зведення замовлень",
     "remove-widget": "Видалити віджет",
     "remove-widget": "Видалити віджет",
     "thisMonth": "Цей місяць",
     "thisMonth": "Цей місяць",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "从销售渠道移除商品变体",
     "remove-product-variant-from-channel": "从销售渠道移除商品变体",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "输入要搜索的资源名称或标签",
     "search-asset-name-or-tag": "输入要搜索的资源名称或标签",
     "search-for-term": "输入搜索条目",
     "search-for-term": "输入搜索条目",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "更多...",
     "more": "更多...",
     "name": "名称",
     "name": "名称",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "没找到任何结果",
     "no-results": "没找到任何结果",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "{ entity }已更新",
     "notify-update-success": "{ entity }已更新",
     "notify-updated-tags-success": "成功更新标签",
     "notify-updated-tags-success": "成功更新标签",
     "okay": "",
     "okay": "",
-    "open": "详情",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "添加窗口工具",
     "add-widget": "添加窗口工具",
     "latest-orders": "最新订单",
     "latest-orders": "最新订单",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "订单预览",
     "orders-summary": "订单预览",
     "remove-widget": "移除窗口",
     "remove-widget": "移除窗口",
     "thisMonth": "本月",
     "thisMonth": "本月",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -158,6 +158,7 @@
     "remove-product-variant-from-channel": "",
     "remove-product-variant-from-channel": "",
     "reorder-collection": "",
     "reorder-collection": "",
     "root-collection": "",
     "root-collection": "",
+    "run-pending-search-index-updates": "",
     "running-search-index-updates": "",
     "running-search-index-updates": "",
     "search-asset-name-or-tag": "",
     "search-asset-name-or-tag": "",
     "search-for-term": "輸入搜索條目",
     "search-for-term": "輸入搜索條目",
@@ -253,6 +254,7 @@
     "medium-date": "",
     "medium-date": "",
     "more": "更多...",
     "more": "更多...",
     "name": "名稱",
     "name": "名稱",
+    "no-alerts": "",
     "no-bulk-actions-available": "",
     "no-bulk-actions-available": "",
     "no-results": "没找到任何結果",
     "no-results": "没找到任何結果",
     "not-applicable": "",
     "not-applicable": "",
@@ -269,7 +271,6 @@
     "notify-update-success": "{ entity }已更新",
     "notify-update-success": "{ entity }已更新",
     "notify-updated-tags-success": "",
     "notify-updated-tags-success": "",
     "okay": "",
     "okay": "",
-    "open": "詳情",
     "operator-contains": "",
     "operator-contains": "",
     "operator-eq": "",
     "operator-eq": "",
     "operator-gt": "",
     "operator-gt": "",
@@ -394,6 +395,10 @@
   "dashboard": {
   "dashboard": {
     "add-widget": "",
     "add-widget": "",
     "latest-orders": "",
     "latest-orders": "",
+    "metric-average-order-value": "",
+    "metric-number-of-orders": "",
+    "metric-order-total-value": "",
+    "metrics": "",
     "orders-summary": "",
     "orders-summary": "",
     "remove-widget": "",
     "remove-widget": "",
     "thisMonth": "",
     "thisMonth": "",

Some files were not shown because too many files changed in this diff