@vendure/dashboard 3.4.2-master-202509030226 → 3.4.2-master-202509040226

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.4.2-master-202509030226",
4
+ "version": "3.4.2-master-202509040226",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -100,8 +100,8 @@
100
100
  "@types/react": "^19.0.10",
101
101
  "@types/react-dom": "^19.0.4",
102
102
  "@uidotdev/usehooks": "^2.4.1",
103
- "@vendure/common": "^3.4.2-master-202509030226",
104
- "@vendure/core": "^3.4.2-master-202509030226",
103
+ "@vendure/common": "^3.4.2-master-202509040226",
104
+ "@vendure/core": "^3.4.2-master-202509040226",
105
105
  "@vitejs/plugin-react": "^4.3.4",
106
106
  "acorn": "^8.11.3",
107
107
  "acorn-walk": "^8.3.2",
@@ -152,5 +152,5 @@
152
152
  "lightningcss-linux-arm64-musl": "^1.29.3",
153
153
  "lightningcss-linux-x64-musl": "^1.29.1"
154
154
  },
155
- "gitHead": "ea17999f2c068b9d5c96d81f4646393acb5baedf"
155
+ "gitHead": "1c3baa9187d1e7b08aa6f1b798aad54770bbed53"
156
156
  }
@@ -31,7 +31,6 @@ export const facetWithValuesFragment = graphql(
31
31
  updatedAt
32
32
  name
33
33
  code
34
- languageCode
35
34
  isPrivate
36
35
  valueList(options: $facetValueListOptions) {
37
36
  totalItems
@@ -1,6 +1,7 @@
1
1
  import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
2
2
  import { FacetValueChip } from '@/vdb/components/shared/facet-value-chip.js';
3
3
  import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
4
+ import { Badge } from '@/vdb/components/ui/badge.js';
4
5
  import { Button } from '@/vdb/components/ui/button.js';
5
6
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
6
7
  import { ListPage } from '@/vdb/framework/page/list-page.js';
@@ -16,12 +17,49 @@ import {
16
17
  } from './components/facet-bulk-actions.js';
17
18
  import { FacetValuesSheet } from './components/facet-values-sheet.js';
18
19
  import { deleteFacetDocument, facetListDocument } from './facets.graphql.js';
20
+ import { DataTableCellComponent } from '@/vdb/components/shared/table-cell/table-cell-types.js';
19
21
 
20
22
  export const Route = createFileRoute('/_authenticated/_facets/facets')({
21
23
  component: FacetListPage,
22
24
  loader: () => ({ breadcrumb: () => <Trans>Facets</Trans> }),
23
25
  });
24
26
 
27
+ const FacetValuesCell: DataTableCellComponent<ResultOf<
28
+ typeof facetListDocument
29
+ >['facets']['items'][0]> = ({ row }) => {
30
+ const value = row.original.valueList;
31
+ if (!value) {
32
+ return null;
33
+ }
34
+ const list = value;
35
+ return (
36
+ <div className="flex flex-wrap gap-2 items-center">
37
+ {list.items.map(item => {
38
+ return (
39
+ <FacetValueChip
40
+ key={item.id}
41
+ facetValue={item}
42
+ removable={false}
43
+ displayFacetName={false}
44
+ />
45
+ );
46
+ })}
47
+ <FacetValuesSheet
48
+ facetId={row.original.id}
49
+ facetName={row.original.name}
50
+ >
51
+ {list.totalItems > 3 ? (
52
+ <div>
53
+ <Trans>+ {list.totalItems - 3} more</Trans>
54
+ </div>
55
+ ) : (
56
+ <Trans>View values</Trans>
57
+ )}
58
+ </FacetValuesSheet>
59
+ </div>
60
+ );
61
+ };
62
+
25
63
  function FacetListPage() {
26
64
  return (
27
65
  <ListPage
@@ -29,49 +67,31 @@ function FacetListPage() {
29
67
  title="Facets"
30
68
  listQuery={facetListDocument}
31
69
  deleteMutation={deleteFacetDocument}
70
+ defaultVisibility={{
71
+ name: true,
72
+ isPrivate: true,
73
+ valueList: true,
74
+ }}
32
75
  customizeColumns={{
33
76
  name: {
34
- header: 'Facet Name',
77
+ header: () => <Trans>Facet Name</Trans>,
35
78
  cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
36
79
  },
37
- valueList: {
38
- header: () => <Trans>Values</Trans>,
39
- cell: ({ cell }) => {
40
- const value = cell.getValue();
41
- if (!value) {
42
- return null;
43
- }
44
- const list = value as any as ResultOf<
45
- typeof facetListDocument
46
- >['facets']['items'][0]['valueList'];
80
+ isPrivate: {
81
+ header: () => <Trans>Visibility</Trans>,
82
+ cell: ({ row }) => {
83
+ const isPrivate = row.original.isPrivate;
47
84
  return (
48
- <div className="flex flex-wrap gap-2 items-center">
49
- {list.items.map(item => {
50
- return (
51
- <FacetValueChip
52
- key={item.id}
53
- facetValue={item}
54
- removable={false}
55
- displayFacetName={false}
56
- />
57
- );
58
- })}
59
- <FacetValuesSheet
60
- facetId={cell.row.original.id}
61
- facetName={cell.row.original.name}
62
- >
63
- {list.totalItems > 3 ? (
64
- <div>
65
- <Trans>+ {list.totalItems - 3} more</Trans>
66
- </div>
67
- ) : (
68
- <Trans>View values</Trans>
69
- )}
70
- </FacetValuesSheet>
71
- </div>
85
+ <Badge variant={isPrivate ? 'destructive' : 'success'}>
86
+ {isPrivate ? <Trans>private</Trans> : <Trans>public</Trans>}
87
+ </Badge>
72
88
  );
73
89
  },
74
90
  },
91
+ valueList: {
92
+ header: () => <Trans>Values</Trans>,
93
+ cell: FacetValuesCell,
94
+ },
75
95
  }}
76
96
  onSearchTermChange={searchTerm => {
77
97
  return {
@@ -1,6 +1,9 @@
1
- import { Money } from '@/vdb/components/data-display/money.js';
2
1
  import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
3
- import { Badge } from '@/vdb/components/ui/badge.js';
2
+ import {
3
+ CustomerCell,
4
+ OrderMoneyCell,
5
+ OrderStateCell,
6
+ } from '@/vdb/components/shared/table-cell/order-table-cell-components.js';
4
7
  import { Button } from '@/vdb/components/ui/button.js';
5
8
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
6
9
  import { ListPage } from '@/vdb/framework/page/list-page.js';
@@ -9,7 +12,7 @@ import { ResultOf } from '@/vdb/graphql/graphql.js';
9
12
  import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
10
13
  import { Trans } from '@/vdb/lib/trans.js';
11
14
  import { useMutation } from '@tanstack/react-query';
12
- import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
15
+ import { createFileRoute, useNavigate } from '@tanstack/react-router';
13
16
  import { PlusIcon } from 'lucide-react';
14
17
  import { createDraftOrderDocument, orderListDocument } from './orders.graphql.js';
15
18
 
@@ -58,26 +61,15 @@ function OrderListPage() {
58
61
  customizeColumns={{
59
62
  total: {
60
63
  header: 'Total',
61
- cell: ({ cell, row }) => {
62
- const value = cell.getValue();
63
- const currencyCode = row.original.currencyCode;
64
- return <Money value={value} currency={currencyCode} />;
65
- },
64
+ cell: OrderMoneyCell,
66
65
  },
67
66
  totalWithTax: {
68
67
  header: 'Total with Tax',
69
- cell: ({ cell, row }) => {
70
- const value = cell.getValue();
71
- const currencyCode = row.original.currencyCode;
72
- return <Money value={value} currency={currencyCode} />;
73
- },
68
+ cell: OrderMoneyCell,
74
69
  },
75
70
  state: {
76
71
  header: 'State',
77
- cell: ({ cell }) => {
78
- const value = cell.getValue() as string;
79
- return <Badge variant="outline">{value}</Badge>;
80
- },
72
+ cell: OrderStateCell,
81
73
  },
82
74
  code: {
83
75
  header: 'Code',
@@ -89,24 +81,12 @@ function OrderListPage() {
89
81
  },
90
82
  customer: {
91
83
  header: 'Customer',
92
- cell: ({ cell }) => {
93
- const value = cell.getValue();
94
- if (!value) {
95
- return null;
96
- }
97
- return (
98
- <Button asChild variant="ghost">
99
- <Link to={`/customers/${value.id}`}>
100
- {value.firstName} {value.lastName}
101
- </Link>
102
- </Button>
103
- );
104
- },
84
+ cell: CustomerCell,
105
85
  },
106
86
  shippingLines: {
107
87
  header: 'Shipping',
108
- cell: ({ cell }) => {
109
- const value = cell.getValue();
88
+ cell: ({ row }) => {
89
+ const value = row.original.shippingLines;
110
90
  return <div>{value.map(line => line.shippingMethod.name).join(', ')}</div>;
111
91
  },
112
92
  },
@@ -135,6 +135,7 @@ export function CreateProductVariantsDialog({
135
135
  ({ data }: { data: VariantConfiguration }) => setVariantData(data),
136
136
  [],
137
137
  );
138
+ const createCount = Object.values(variantData?.variants ?? {}).filter(v => v.enabled).length;
138
139
 
139
140
  return (
140
141
  <>
@@ -168,7 +169,8 @@ export function CreateProductVariantsDialog({
168
169
  !variantData ||
169
170
  createOptionGroupMutation.isPending ||
170
171
  addOptionGroupToProductMutation.isPending ||
171
- createProductVariantsMutation.isPending
172
+ createProductVariantsMutation.isPending ||
173
+ createCount === 0
172
174
  }
173
175
  >
174
176
  {createOptionGroupMutation.isPending ||
@@ -176,13 +178,7 @@ export function CreateProductVariantsDialog({
176
178
  createProductVariantsMutation.isPending ? (
177
179
  <Trans>Creating...</Trans>
178
180
  ) : (
179
- <Trans>
180
- Create{' '}
181
- {variantData
182
- ? Object.values(variantData.variants).filter(v => v.enabled).length
183
- : 0}{' '}
184
- variants
185
- </Trans>
181
+ <Trans>Create {createCount} variants</Trans>
186
182
  )}
187
183
  </Button>
188
184
  </DialogFooter>
@@ -225,6 +225,7 @@ export function CreateProductVariants({
225
225
  <FormItem className="flex items-center space-x-2">
226
226
  <FormControl>
227
227
  <Checkbox
228
+ defaultChecked={true}
228
229
  checked={field.value}
229
230
  onCheckedChange={field.onChange}
230
231
  />
@@ -53,6 +53,24 @@ export function DataTableDateTimeFilter({
53
53
  }
54
54
  }, [operator, value, startDate, endDate]);
55
55
 
56
+ const parseToDate = (input: unknown): Date | undefined => {
57
+ if (input instanceof Date) {
58
+ return input;
59
+ }
60
+ if (typeof input === 'string' && input !== '') {
61
+ return new Date(input);
62
+ }
63
+ return;
64
+ };
65
+
66
+ const dashboardComponentProps = {
67
+ name: 'date',
68
+ onBlur: () => {
69
+ /* */
70
+ },
71
+ ref: () => null,
72
+ };
73
+
56
74
  return (
57
75
  <div className="flex flex-col gap-2">
58
76
  <div className="flex flex-col md:flex-row gap-2">
@@ -71,11 +89,23 @@ export function DataTableDateTimeFilter({
71
89
  {operator !== 'isNull' &&
72
90
  (operator === 'between' ? (
73
91
  <div className="space-y-2">
74
- <DateTimeInput value={startDate} onChange={setStartDate} />
75
- <DateTimeInput value={endDate} onChange={setEndDate} />
92
+ <DateTimeInput
93
+ {...dashboardComponentProps}
94
+ value={startDate}
95
+ onChange={val => setStartDate(parseToDate(val))}
96
+ />
97
+ <DateTimeInput
98
+ {...dashboardComponentProps}
99
+ value={endDate}
100
+ onChange={val => setEndDate(parseToDate(val))}
101
+ />
76
102
  </div>
77
103
  ) : (
78
- <DateTimeInput value={value} onChange={setValue} />
104
+ <DateTimeInput
105
+ {...dashboardComponentProps}
106
+ value={value}
107
+ onChange={val => setValue(parseToDate(val))}
108
+ />
79
109
  ))}
80
110
  </div>
81
111
  {error && <p className="text-sm text-red-500">{error}</p>}
@@ -360,7 +360,10 @@ export function AssetGallery({
360
360
  {formatFileSize(asset.fileSize)}
361
361
  </p>
362
362
  )}
363
- <DetailPageButton id={asset.id} label={<Trans>Edit</Trans>} />
363
+ <DetailPageButton
364
+ href={`/assets/${asset.id}`}
365
+ label={<Trans>Edit</Trans>}
366
+ />
364
367
  </div>
365
368
  </CardContent>
366
369
  </Card>
@@ -0,0 +1,38 @@
1
+ import { Money } from '@/vdb/components/data-display/money.js';
2
+ import { DataTableCellComponent } from '@/vdb/components/shared/table-cell/table-cell-types.js';
3
+ import { Badge } from '@/vdb/components/ui/badge.js';
4
+ import { Button } from '@/vdb/components/ui/button.js';
5
+ import { Link } from '@tanstack/react-router';
6
+
7
+ type CustomerCellData = {
8
+ customer: {
9
+ id: string;
10
+ firstName: string;
11
+ lastName: string;
12
+ } | null;
13
+ };
14
+
15
+ export const CustomerCell: DataTableCellComponent<CustomerCellData> = ({ row }) => {
16
+ const value = row.original.customer;
17
+ if (!value) {
18
+ return null;
19
+ }
20
+ return (
21
+ <Button asChild variant="ghost">
22
+ <Link to={`/customers/${value.id}`}>
23
+ {value.firstName} {value.lastName}
24
+ </Link>
25
+ </Button>
26
+ );
27
+ };
28
+
29
+ export const OrderStateCell: DataTableCellComponent<{ state: string }> = ({ row }) => {
30
+ const value = row.original.state;
31
+ return <Badge variant="outline">{value}</Badge>;
32
+ };
33
+
34
+ export const OrderMoneyCell: DataTableCellComponent<{ currencyCode: string }> = ({ cell, row }) => {
35
+ const value = cell.getValue();
36
+ const currencyCode = row.original.currencyCode;
37
+ return <Money value={value} currency={currencyCode} />;
38
+ };
@@ -0,0 +1,33 @@
1
+ import { CellContext } from '@tanstack/table-core';
2
+
3
+ /**
4
+ * @description
5
+ * This type is used to define re-usable components that can render a table cell in a
6
+ * DataTable.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * type CustomerCellData = {
11
+ * customer: {
12
+ * id: string;
13
+ * firstName: string;
14
+ * lastName: string;
15
+ * } | null;
16
+ * };
17
+ *
18
+ * export const CustomerCell: DataTableCellComponent<CustomerCellData> = ({ row }) => {
19
+ * const value = row.original.customer;
20
+ * if (!value) {
21
+ * return null;
22
+ * }
23
+ * return (
24
+ * <Button asChild variant="ghost">
25
+ * <Link to={`/customers/${value.id}`}>
26
+ * {value.firstName} {value.lastName}
27
+ * </Link>
28
+ * </Button>
29
+ * );
30
+ * };
31
+ * ```
32
+ */
33
+ export type DataTableCellComponent<T> = <Data extends T>(context: CellContext<Data, any>) => any;
@@ -1,4 +1,9 @@
1
1
  import { PaginatedListDataTable } from '@/vdb/components/shared/paginated-list-data-table.js';
2
+ import {
3
+ CustomerCell,
4
+ OrderMoneyCell,
5
+ OrderStateCell,
6
+ } from '@/vdb/components/shared/table-cell/order-table-cell-components.js';
2
7
  import { Button } from '@/vdb/components/ui/button.js';
3
8
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
4
9
  import { Link } from '@tanstack/react-router';
@@ -25,7 +30,6 @@ export function LatestOrdersWidget() {
25
30
  return (
26
31
  <DashboardBaseWidget id={WIDGET_ID} title="Latest Orders" description="Your latest orders">
27
32
  <PaginatedListDataTable
28
- disableViewOptions
29
33
  page={page}
30
34
  transformVariables={variables => ({
31
35
  ...variables,
@@ -38,6 +42,7 @@ export function LatestOrdersWidget() {
38
42
  state: {
39
43
  notIn: ['Cancelled', 'Draft'],
40
44
  },
45
+ ...(variables.options?.filter ?? {}),
41
46
  },
42
47
  },
43
48
  })}
@@ -56,7 +61,7 @@ export function LatestOrdersWidget() {
56
61
  header: 'Placed At',
57
62
  cell: ({ row }) => {
58
63
  return (
59
- <span>
64
+ <span className="capitalize">
60
65
  {formatRelative(row.original.orderPlacedAt ?? new Date(), new Date())}
61
66
  </span>
62
67
  );
@@ -64,12 +69,11 @@ export function LatestOrdersWidget() {
64
69
  },
65
70
  total: {
66
71
  header: 'Total',
67
- cell: ({ row }) => {
68
- return (
69
- <span>{formatCurrency(row.original.total, row.original.currencyCode)}</span>
70
- );
71
- },
72
+ cell: OrderMoneyCell,
72
73
  },
74
+ totalWithTax: { cell: OrderMoneyCell },
75
+ state: { cell: OrderStateCell },
76
+ customer: { cell: CustomerCell },
73
77
  }}
74
78
  itemsPerPage={pageSize}
75
79
  sorting={sorting}
@@ -1,6 +1,4 @@
1
- import { Area, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
2
-
3
- import { AreaChart } from 'recharts';
1
+ import { Area, AreaChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
4
2
  import { useWidgetDimensions } from '../base-widget.js';
5
3
 
6
4
  export function MetricsChart({
@@ -14,11 +12,29 @@ export function MetricsChart({
14
12
 
15
13
  return (
16
14
  <AreaChart width={width} height={height} data={chartData}>
15
+ <defs>
16
+ <linearGradient id="gradientFill" x1="0" y1="0" x2="0" y2="1">
17
+ <stop offset="5%" stopColor="var(--color-brand)" stopOpacity={0.9} />
18
+ <stop offset="100%" stopColor="var(--color-brand)" stopOpacity={0.05} />
19
+ </linearGradient>
20
+ </defs>
17
21
  <CartesianGrid strokeDasharray="4 4" stroke="var(--color-border)" />
18
22
  <XAxis className="text-xs" color="var(--color-foreground)" dataKey="name" interval={2} />
19
23
  <YAxis className="text-xs" color="var(--color-foreground)" tickFormatter={formatValue} />
20
- <Tooltip formatter={formatValue} />
21
- <Area type="monotone" dataKey="sales" stroke="var(--color-brand)" strokeWidth={2} />
24
+ <Tooltip
25
+ formatter={formatValue}
26
+ contentStyle={{ borderRadius: 4, padding: 4, paddingLeft: 8, paddingRight: 8 }}
27
+ labelStyle={{ fontSize: 12 }}
28
+ itemStyle={{ fontSize: 14 }}
29
+ />
30
+ <Area
31
+ type="monotone"
32
+ dataKey="sales"
33
+ stroke="var(--color-brand)"
34
+ strokeWidth={2}
35
+ strokeOpacity={0.8}
36
+ fill={'url(#gradientFill)'}
37
+ />
22
38
  </AreaChart>
23
39
  );
24
40
  }
@@ -1,8 +1,10 @@
1
+ import { Button } from '@/vdb/components/ui/button.js';
1
2
  import { Tabs, TabsList, TabsTrigger } from '@/vdb/components/ui/tabs.js';
2
3
  import { api } from '@/vdb/graphql/api.js';
3
4
  import { useChannel } from '@/vdb/hooks/use-channel.js';
4
5
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
5
6
  import { useQuery } from '@tanstack/react-query';
7
+ import { RefreshCw } from 'lucide-react';
6
8
  import { useMemo, useState } from 'react';
7
9
  import { DashboardBaseWidget } from '../base-widget.js';
8
10
  import { MetricsChart } from './chart.js';
@@ -19,7 +21,7 @@ export function MetricsWidget() {
19
21
  const { activeChannel } = useChannel();
20
22
  const [dataType, setDataType] = useState<DATA_TYPES>(DATA_TYPES.OrderTotal);
21
23
 
22
- const { data } = useQuery({
24
+ const { data, isRefetching, refetch } = useQuery({
23
25
  queryKey: ['dashboard-order-metrics', dataType],
24
26
  queryFn: () => {
25
27
  return api.query(orderChartDataQuery, {
@@ -53,15 +55,22 @@ export function MetricsWidget() {
53
55
  <DashboardBaseWidget
54
56
  id="metrics-widget"
55
57
  title="Metrics"
56
- description="Metrics widget"
58
+ description="Order metrics"
57
59
  actions={
58
- <Tabs defaultValue={dataType} onValueChange={value => setDataType(value as DATA_TYPES)}>
59
- <TabsList>
60
- <TabsTrigger value={DATA_TYPES.OrderCount}>Order Count</TabsTrigger>
61
- <TabsTrigger value={DATA_TYPES.OrderTotal}>Order Total</TabsTrigger>
62
- <TabsTrigger value={DATA_TYPES.AverageOrderValue}>Average Order Value</TabsTrigger>
63
- </TabsList>
64
- </Tabs>
60
+ <div className="flex gap-1">
61
+ <Tabs defaultValue={dataType} onValueChange={value => setDataType(value as DATA_TYPES)}>
62
+ <TabsList>
63
+ <TabsTrigger value={DATA_TYPES.OrderCount}>Order Count</TabsTrigger>
64
+ <TabsTrigger value={DATA_TYPES.OrderTotal}>Order Total</TabsTrigger>
65
+ <TabsTrigger value={DATA_TYPES.AverageOrderValue}>
66
+ Average Order Value
67
+ </TabsTrigger>
68
+ </TabsList>
69
+ </Tabs>
70
+ <Button variant={'ghost'} onClick={() => refetch()}>
71
+ <RefreshCw className={isRefetching ? 'animate-rotate' : ''} />
72
+ </Button>
73
+ </div>
65
74
  }
66
75
  >
67
76
  {chartData && (
@@ -137,26 +137,28 @@ export function OrdersSummaryWidget() {
137
137
  </Tabs>
138
138
  }
139
139
  >
140
- <div className="flex flex-col lg:gap-4 items-center justify-center text-center">
141
- <div className="flex flex-col lg:gap-2">
142
- <p className="lg:text-lg text-muted-foreground">Total Orders</p>
143
- <p className="text-3xl font-semibold">
144
- <AnimatedNumber
145
- animationConfig={{ mass: 0.5, stiffness: 90, damping: 10 }}
146
- value={currentTotalOrders}
147
- />
148
- </p>
149
- <PercentageChange value={orderChange} />
150
- </div>
151
- <div className="flex flex-col lg:gap-2">
152
- <p className="lg:text-lg text-muted-foreground">Total Revenue</p>
153
- <p className="text-3xl font-semibold">
154
- <AnimatedCurrency
155
- animationConfig={{ mass: 0.2, stiffness: 90, damping: 10 }}
156
- value={currentRevenue}
157
- />
158
- </p>
159
- <PercentageChange value={revenueChange} />
140
+ <div className="@container h-full">
141
+ <div className="flex flex-col h-full @md:flex-row gap-8 items-center justify-center @md:justify-evenly text-center tabular-nums">
142
+ <div className="flex flex-col lg:gap-2">
143
+ <p className="lg:text-lg text-muted-foreground">Total Orders</p>
144
+ <p className="text-xl @md:text-3xl font-semibold">
145
+ <AnimatedNumber
146
+ animationConfig={{ mass: 0.01, stiffness: 90, damping: 3 }}
147
+ value={currentTotalOrders}
148
+ />
149
+ </p>
150
+ <PercentageChange value={orderChange} />
151
+ </div>
152
+ <div className="flex flex-col lg:gap-2">
153
+ <p className="lg:text-lg text-muted-foreground">Total Revenue</p>
154
+ <p className="text-xl @md:text-3xl font-semibold">
155
+ <AnimatedCurrency
156
+ animationConfig={{ mass: 0.01, stiffness: 90, damping: 3 }}
157
+ value={currentRevenue}
158
+ />
159
+ </p>
160
+ <PercentageChange value={revenueChange} />
161
+ </div>
160
162
  </div>
161
163
  </div>
162
164
  </DashboardBaseWidget>