Skip to content

Commit 246a450

Browse files
authored
feat: order summary chart visibility (#346)
* feat(ui): add showChart property to orders summary block and update related components * feat(ui): enhance Payments Summary block with chart support and layout options * refactor(ui): reverted strapi integrations * refactor(ui): reverted strapi implementation * feat(ui): add layout option to Orders Summary block * fix(ui): update order details mapping to handle optional CMS fields
1 parent 9350b4a commit 246a450

File tree

19 files changed

+565
-173
lines changed

19 files changed

+565
-173
lines changed

packages/blocks/order-details/src/api-harmonization/order-details.mapper.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,29 @@ export const mapOrderDetails = (
3333
value: order.id,
3434
},
3535
subtotal: {
36-
title: cms.totalValue.title,
37-
icon: cms.totalValue.icon,
36+
title: cms.totalValue?.title || '',
37+
icon: cms.totalValue?.icon,
3838
label: Utils.Price.checkNegativeValue(order.subtotal || { value: 0, currency }).value.toString(),
39-
description: format(cms.totalValue.message || '', {
39+
description: format(cms.totalValue?.message || '', {
4040
value: order.items.total,
4141
}),
4242
value: order.subtotal,
4343
},
4444
createdAt: {
45-
title: cms.createdOrderAt.title,
45+
title: cms.createdOrderAt?.title || '',
4646
label: Utils.Date.formatDateRelative(
4747
order.createdAt,
4848
locale,
4949
cms.labels.today,
5050
cms.labels.yesterday,
5151
timezone,
5252
),
53-
icon: cms.createdOrderAt.icon,
53+
icon: cms.createdOrderAt?.icon,
5454
description: Utils.Date.formatTime(order.createdAt, locale, timezone),
5555
value: order.createdAt,
5656
},
5757
paymentDueDate: {
58-
title: cms.paymentDueDate.title,
58+
title: cms.paymentDueDate?.title || '',
5959
label: order.paymentDueDate
6060
? Utils.Date.formatDateRelative(
6161
order.paymentDueDate,
@@ -65,41 +65,41 @@ export const mapOrderDetails = (
6565
timezone,
6666
)
6767
: '-',
68-
icon: cms.paymentDueDate.icon,
68+
icon: cms.paymentDueDate?.icon,
6969
description: order.documents?.[0]?.id
70-
? format(cms.paymentDueDate.message || '', {
70+
? format(cms.paymentDueDate?.message || '', {
7171
value: order.documents?.[0]?.id,
7272
})
73-
: cms.paymentDueDate.altMessage,
73+
: cms.paymentDueDate?.altMessage,
7474
value: order.paymentDueDate,
7575
},
7676
overdue: {
77-
title: cms.overdue.title,
78-
icon: cms.overdue.icon,
77+
title: cms.overdue?.title || '',
78+
icon: cms.overdue?.icon,
7979
label: Utils.Price.checkNegativeValue({ value: overdueAmount, currency }).value.toString(),
8080
description: isOverdue
81-
? format(cms.overdue.message || '', {
81+
? format(cms.overdue?.message || '', {
8282
days: overdueDays,
8383
})
84-
: cms.overdue.altMessage,
84+
: cms.overdue?.altMessage,
8585
value: { value: Utils.Price.checkNegativeValue({ value: overdueAmount, currency }).value, currency },
8686
isOverdue,
8787
},
8888
status: {
89-
title: cms.orderStatus.title,
90-
icon: cms.orderStatus.icon,
89+
title: cms.orderStatus?.title || '',
90+
icon: cms.orderStatus?.icon,
9191
label: cms.fieldMapping.status?.[order.status] || order.status,
9292
value: order.status,
9393
statusLadder: cms.statusLadder,
9494
},
9595
customerComment: {
96-
title: cms.customerComment.title,
97-
icon: cms.customerComment.icon,
96+
title: cms.customerComment?.title || '',
97+
icon: cms.customerComment?.icon,
9898
value: order.customerComment,
9999
link: {
100-
label: cms.customerComment.link?.label,
101-
icon: cms.customerComment.link?.icon,
102-
url: cms.customerComment.link?.url,
100+
label: cms.customerComment?.link?.label,
101+
icon: cms.customerComment?.link?.icon,
102+
url: cms.customerComment?.link?.url,
103103
},
104104
},
105105
},

packages/blocks/orders-summary/src/api-harmonization/orders-summary.mapper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ export const mapOrdersSummary = (
6161
title: cms.chart.title,
6262
data: getChartData(ordersPrevious, ordersCurrent, range, diff, locale),
6363
legend: cms.chart.legend,
64+
showChart: cms.chart.showChart,
6465
},
6566
noResults: cms.noResults,
6667
ranges: cms.ranges,
68+
layout: cms.layout,
6769
};
6870
};
6971

packages/blocks/orders-summary/src/api-harmonization/orders-summary.model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ export class OrdersSummaryBlock extends ApiModels.Block.Block {
3333
prev: string;
3434
current: string;
3535
};
36+
showChart?: boolean;
3637
};
3738
ranges!: CMS.Model.OrdersSummaryBlock.OrdersSummaryBlock['ranges'];
39+
layout?: 'vertical' | 'horizontal';
3840
noResults!: CMS.Model.OrdersSummaryBlock.OrdersSummaryBlock['noResults'];
3941
}
4042

packages/blocks/orders-summary/src/frontend/OrdersSummary.client.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const Default: Story = {
8282
prev: 'Previous period',
8383
current: 'Current period',
8484
},
85+
showChart: true,
8586
},
8687
noResults: {
8788
title: "So far, there's nothing here",

packages/blocks/orders-summary/src/frontend/OrdersSummary.client.tsx

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export const OrdersSummaryPure: React.FC<Readonly<OrdersSummaryPureProps>> = ({
6666

6767
const [isPending, startTransition] = useTransition();
6868

69+
const cardsLayout = component.layout === 'horizontal' ? 'flex-col md:flex-row' : 'flex-col';
70+
6971
const handleFilter = (value: string) => {
7072
startTransition(async () => {
7173
let dateFrom: string;
@@ -125,20 +127,70 @@ export const OrdersSummaryPure: React.FC<Readonly<OrdersSummaryPureProps>> = ({
125127
)}
126128
</div>
127129

128-
<div className="w-full flex flex-col lg:flex-row gap-6">
129-
<div className="w-full flex flex-col gap-6">
130-
<InfoCard
131-
title={data.totalValue.title}
132-
value={
133-
<Typography variant="highlightedBig">
134-
<Price price={data.totalValue.value} />
135-
</Typography>
136-
}
137-
description={<Trend value={data.totalValue.trend} />}
138-
icon={data.totalValue.icon}
139-
/>
140-
141-
<div className="w-full flex flex-col sm:flex-row gap-6">
130+
<div
131+
className={cn('w-full flex gap-6', data.chart.showChart ? 'flex-col lg:flex-row' : 'flex-col')}
132+
>
133+
{data.chart.showChart ? (
134+
<>
135+
<div className={cn('w-full flex gap-6', cardsLayout)}>
136+
<InfoCard
137+
title={data.totalValue.title}
138+
value={
139+
<Typography variant="highlightedBig">
140+
<Price price={data.totalValue.value} />
141+
</Typography>
142+
}
143+
description={<Trend value={data.totalValue.trend} />}
144+
icon={data.totalValue.icon}
145+
/>
146+
147+
<div className="w-full flex flex-col sm:flex-row gap-6">
148+
<InfoCard
149+
title={data.averageValue.title}
150+
value={
151+
<Typography variant="highlightedBig">
152+
<Price price={data.averageValue.value} />
153+
</Typography>
154+
}
155+
description={<Trend value={data.averageValue.trend} />}
156+
icon={data.averageValue.icon}
157+
/>
158+
<InfoCard
159+
title={data.averageNumber.title}
160+
value={
161+
<Typography variant="highlightedBig">
162+
{data.averageNumber.value}
163+
</Typography>
164+
}
165+
description={<Trend value={data.averageNumber.trend} />}
166+
icon={data.averageNumber.icon}
167+
/>
168+
</div>
169+
</div>
170+
<Card className="h-full w-full">
171+
<div className="p-6 flex flex-col gap-6">
172+
<Typography variant="subtitle">{data.chart.title}</Typography>
173+
174+
<DoubleLineChart
175+
chartData={data.chart.data}
176+
legend={data.chart.legend}
177+
tooltipType="number"
178+
/>
179+
</div>
180+
</Card>
181+
</>
182+
) : (
183+
<div className={cn('w-full flex gap-6', cardsLayout)}>
184+
<InfoCard
185+
title={data.totalValue.title}
186+
value={
187+
<Typography variant="highlightedBig">
188+
<Price price={data.totalValue.value} />
189+
</Typography>
190+
}
191+
description={<Trend value={data.totalValue.trend} />}
192+
icon={data.totalValue.icon}
193+
/>
142194
<InfoCard
143195
title={data.averageValue.title}
144196
value={
@@ -156,19 +208,7 @@ export const OrdersSummaryPure: React.FC<Readonly<OrdersSummaryPureProps>> = ({
156208
icon={data.averageNumber.icon}
157209
/>
158210
</div>
159-
</div>
160-
161-
<Card className="h-full w-full">
162-
<div className="p-6 flex flex-col gap-6">
163-
<Typography variant="subtitle">{data.chart.title}</Typography>
164-
165-
<DoubleLineChart
166-
chartData={data.chart.data}
167-
legend={data.chart.legend}
168-
tooltipType="number"
169-
/>
170-
</div>
171-
</Card>
211+
)}
172212
</div>
173213
</div>
174214
</LoadingOverlay>

packages/blocks/payments-summary/src/api-harmonization/payments-summary.mapper.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Utils } from '@o2s/utils.api-harmonization';
66

77
import { Models } from '@o2s/framework/modules';
88

9-
import { PaymentsSummaryBlock } from './payments-summary.model';
9+
import { BarData, PaymentsSummaryBlock } from './payments-summary.model';
1010

1111
export const mapPaymentsSummary = (
1212
cms: CMS.Model.PaymentsSummaryBlock.PaymentsSummaryBlock,
@@ -33,11 +33,14 @@ export const mapPaymentsSummary = (
3333
return acc + invoice.totalToBePaid.value;
3434
}, 0);
3535

36-
return {
36+
const result: PaymentsSummaryBlock = {
3737
__typename: 'PaymentsSummaryBlock',
3838
id: cms.id,
3939
currency: currency,
40-
overdue: {
40+
};
41+
42+
if (cms.overdue) {
43+
result.overdue = {
4144
title: cms.overdue.title,
4245
link: cms.overdue.link,
4346
description: isOverdue
@@ -48,13 +51,79 @@ export const mapPaymentsSummary = (
4851
value: { value: Utils.Price.checkNegativeValue({ value: overdueAmount, currency }).value, currency },
4952
isOverdue: isOverdue,
5053
icon: cms.overdue.icon,
51-
},
52-
toBePaid: {
54+
};
55+
}
56+
57+
if (cms.toBePaid) {
58+
result.toBePaid = {
5359
title: cms.toBePaid.title,
5460
icon: cms.toBePaid.icon,
5561
description: toBePaidAmount > 0 ? cms.toBePaid?.message : cms.toBePaid?.altMessage,
5662
link: cms.toBePaid.link,
5763
value: { value: Utils.Price.checkNegativeValue({ value: toBePaidAmount, currency }).value, currency },
58-
},
59-
};
64+
};
65+
}
66+
67+
if (cms.layout) {
68+
result.layout = cms.layout;
69+
}
70+
71+
if (cms.chart) {
72+
result.chart = {
73+
title: cms.chart.title,
74+
labels: {
75+
topSegment: cms.chart.topSegment,
76+
middleSegment: cms.chart.middleSegment,
77+
bottomSegment: cms.chart.bottomSegment,
78+
total: cms.chart.total,
79+
},
80+
chartData: mapChartData(invoices.data, _locale, cms.chart.monthsToShow),
81+
showChart: cms.chart.showChart,
82+
monthsToShow: cms.chart.monthsToShow,
83+
};
84+
}
85+
86+
return result;
87+
};
88+
89+
const mapChartData = (data: Invoices.Model.Invoice[], locale: string, monthsToShow: number = 6): BarData[] => {
90+
const now = new Date();
91+
const monthsToShowAgo = new Date(now.getFullYear(), now.getMonth() - monthsToShow - 1, 1);
92+
93+
const months = Array.from({ length: monthsToShow }, (_, i) => {
94+
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
95+
return {
96+
month: date.toLocaleString(locale, { month: 'short' }),
97+
topSegment: 0,
98+
middleSegment: 0,
99+
bottomSegment: 0,
100+
total: 0,
101+
date: date,
102+
};
103+
}).reverse();
104+
105+
// Sum up invoice amounts for each month
106+
data.forEach((invoice) => {
107+
const invoiceDate = new Date(invoice.issuedDate);
108+
if (invoiceDate >= monthsToShowAgo) {
109+
const month = months.find(
110+
(m) =>
111+
m.date.getMonth() === invoiceDate.getMonth() && m.date.getFullYear() === invoiceDate.getFullYear(),
112+
);
113+
if (month) {
114+
month.topSegment += invoice.paymentStatus === 'PAYMENT_PAST_DUE' ? invoice.totalAmountDue.value : 0;
115+
month.middleSegment += invoice.paymentStatus === 'PAYMENT_DUE' ? invoice.totalAmountDue.value : 0;
116+
month.bottomSegment += invoice.paymentStatus === 'PAYMENT_COMPLETE' ? invoice.totalAmountDue.value : 0;
117+
month.total += invoice.totalAmountDue.value;
118+
}
119+
}
120+
});
121+
122+
return months.map((month) => ({
123+
month: month.month,
124+
topSegment: month.topSegment.toFixed(2),
125+
middleSegment: month.middleSegment.toFixed(2),
126+
bottomSegment: month.bottomSegment.toFixed(2),
127+
total: month.total.toFixed(2),
128+
}));
60129
};

packages/blocks/payments-summary/src/api-harmonization/payments-summary.model.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Invoices, Models } from '@o2s/framework/modules';
55
export class PaymentsSummaryBlock extends ApiModels.Block.Block {
66
__typename!: 'PaymentsSummaryBlock';
77
currency!: Invoices.Model.Invoice['currency'];
8-
overdue!: {
8+
overdue?: {
99
title: string;
1010
icon?: string;
1111
value: Models.Price.Price;
@@ -17,7 +17,7 @@ export class PaymentsSummaryBlock extends ApiModels.Block.Block {
1717
};
1818
isOverdue: boolean;
1919
};
20-
toBePaid!: {
20+
toBePaid?: {
2121
title: string;
2222
icon?: string;
2323
value: Models.Price.Price;
@@ -28,4 +28,25 @@ export class PaymentsSummaryBlock extends ApiModels.Block.Block {
2828
icon?: string;
2929
};
3030
};
31+
layout?: 'vertical' | 'horizontal';
32+
chart?: {
33+
title?: string;
34+
labels: {
35+
topSegment: string;
36+
middleSegment: string;
37+
bottomSegment: string;
38+
total: string;
39+
};
40+
chartData: BarData[];
41+
showChart?: boolean;
42+
monthsToShow?: number;
43+
};
44+
}
45+
46+
export class BarData {
47+
month!: string;
48+
topSegment!: string;
49+
middleSegment!: string;
50+
bottomSegment!: string;
51+
total!: string;
3152
}

0 commit comments

Comments
 (0)