Skip to content

Commit 190c448

Browse files
committed
[IMP] website_sale: design ecommerce dashboard
This commit adapts the ecommerce dashboard design by: - emphasizing the cards when there is data to display. - making the card active on click. - hiding the "To confirm" card when there is no data to display because it's not a common case. task-5177259
1 parent 30351d5 commit 190c448

File tree

7 files changed

+165
-104
lines changed

7 files changed

+165
-104
lines changed

addons/website_sale/__manifest__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@
161161
'website_sale/static/src/scss/kanban_record.scss',
162162
'website_sale/static/src/js/website_sale_dashboard/**/*',
163163
'website_sale/static/src/views/**/*',
164+
('remove', 'website_sale/static/src/js/website_sale_dashboard/**/*.dark.scss'),
165+
],
166+
"web.assets_web_dark": [
167+
'website_sale/static/src/js/website_sale_dashboard/**/*.dark.scss',
164168
],
165169
'website.website_builder_assets': [
166170
'website_sale/static/src/js/website_sale_form_editor.js',

addons/website_sale/static/src/js/website_sale_dashboard/date_filter_button/date_filter_button.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class DateFilterButton extends Component {
2626
static template = 'website_sale.DateFilterButton';
2727
static components = { Dropdown, DropdownItem };
2828
static props = {
29-
selectedFilter: {
29+
selectedDateFilter: {
3030
type: Object,
3131
optional: true,
3232
shape: {

addons/website_sale/static/src/js/website_sale_dashboard/date_filter_button/date_filter_button.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<templates>
33
<t t-name="website_sale.DateFilterButton">
4-
<Dropdown navigationOptions="{ 'shouldFocusChildInput': false }">
5-
<button class="btn btn-secondary">
4+
<Dropdown navigationOptions="{ 'shouldFocusChildInput': false }" position="'bottom-end'">
5+
<button class="px-4 border-0 bg-transparent fw-normal">
66
<i class="fa fa-calendar me-2"/>
7-
<span t-esc="props.selectedFilter.label"/>
7+
<span t-esc="props.selectedDateFilter.label"/>
8+
<i class="fa fa-caret-down ms-2"/>
89
</button>
910
<t t-set-slot="content">
1011
<t t-foreach="dateFilters" t-as="filter" t-key="filter.id">
1112
<DropdownItem
1213
tag="'div'"
13-
class="{ 'selected': props.selectedFilter.id === filter.id, 'd-flex justify-content-between': true }"
14+
class="{ 'selected': props.selectedDateFilter.id === filter.id, 'd-flex justify-content-between': true }"
1415
closingMode="'none'"
1516
onSelected="() => this.props.update(filter)"
1617
>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.o_dashboard_card {
2+
&--purple {
3+
background-color: $purple-200;
4+
5+
&.o_dashboard_card--active, &:hover, &:active {
6+
background-color: $purple-300;
7+
}
8+
}
9+
10+
&--orange {
11+
background-color: $orange-200;
12+
13+
&.o_dashboard_card--active, &:hover, &:active {
14+
background-color: $orange-300;
15+
}
16+
}
17+
18+
&--cyan {
19+
background-color: $cyan-200;
20+
21+
&.o_dashboard_card--active, &:hover, &:active {
22+
background-color: $cyan-300;
23+
}
24+
}
25+
}

addons/website_sale/static/src/js/website_sale_dashboard/website_sale_dashboard.js

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useService } from '@web/core/utils/hooks';
1+
import { useService, useBus } from '@web/core/utils/hooks';
22
import { Component, onWillStart, onWillUpdateProps, useState } from '@odoo/owl';
33
import { DateFilterButton, DATE_OPTIONS } from './date_filter_button/date_filter_button';
44

@@ -10,10 +10,17 @@ export class WebsiteSaleDashboard extends Component {
1010
setup() {
1111
this.state = useState({
1212
eCommerceData: {},
13-
selectedFilter: DATE_OPTIONS[0],
13+
selectedDateFilter: DATE_OPTIONS[0],
14+
selectedFilter: [],
1415
});
1516
this.orm = useService('orm');
1617

18+
useBus(this.env.searchModel, 'update', () => {
19+
if(!this.isSameFilter(this.state.selectedFilter)) {
20+
this.state.selectedFilter = null;
21+
}
22+
});
23+
1724
onWillStart(async () => {
1825
await this.updateDashboardState();
1926
});
@@ -24,10 +31,10 @@ export class WebsiteSaleDashboard extends Component {
2431

2532
async updateDashboardState(filter = false) {
2633
if (filter) {
27-
this.state.selectedFilter = filter;
34+
this.state.selectedDateFilter = filter;
2835
}
2936
this.state.eCommerceData = await this.orm.call('sale.order', 'retrieve_dashboard', [
30-
this.state.selectedFilter.id,
37+
this.state.selectedDateFilter.id,
3138
]);
3239
}
3340

@@ -45,6 +52,15 @@ export class WebsiteSaleDashboard extends Component {
4552
for (const item of searchItems) {
4653
this.env.searchModel.toggleSearchItem(item.id);
4754
}
55+
this.state.selectedFilter = filters;
56+
}
57+
58+
isSameFilter(filters) {
59+
if (!filters) {
60+
return false;
61+
}
62+
const activeSearchFilterNames = this.env.searchModel.getSearchItems(el => el.isActive && el.type === 'filter')?.map(el => el.name).sort();
63+
return filters.length === activeSearchFilterNames?.length && filters.sort().every((val, i) => val === activeSearchFilterNames[i]);
4864
}
4965

5066
getPeriodCardClass(dataName) {
@@ -59,6 +75,26 @@ export class WebsiteSaleDashboard extends Component {
5975
) {
6076
return 'text-danger';
6177
}
62-
return 'text-muted';
78+
return '';
79+
}
80+
81+
getDashboardCardAdditionalClass(filterName) {
82+
const dashboardCardColor = {
83+
'to_fulfill': 'purple',
84+
'to_confirm': 'orange',
85+
'to_invoice': 'cyan',
86+
};
87+
let dashboardCardClasses = [];
88+
const noData = this.state.eCommerceData['overall'][filterName] == 0;
89+
if(noData) {
90+
dashboardCardClasses.push('bg-secondary text-secondary-emphasis disabled');
91+
} else {
92+
dashboardCardClasses.push('o_dashboard_card--' + dashboardCardColor[filterName]);
93+
}
94+
const filters = filterName == 'to_confirm' ? [filterName, 'from_website'] : [filterName, 'from_website','order_confirmed'];
95+
if(this.isSameFilter(filters)) {
96+
dashboardCardClasses.push('o_dashboard_card--active');
97+
}
98+
return dashboardCardClasses.join(' ');
6399
}
64100
}
Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,67 @@
11
.o_dashboard_card {
2-
min-width: 120px;
3-
min-height: 90px;
4-
align-content: center;
5-
}
2+
flex-basis: 33%;
63

7-
.o_purple_card {
8-
background-color: $purple-300;
9-
}
4+
@include media-breakpoint-up(md) {
5+
max-width: 8rem;
6+
}
107

11-
.o_orange_card {
12-
background-color: $orange-300;
13-
}
8+
&--purple.btn {
9+
background-color: $purple-100;
10+
color: $purple-600;
1411

15-
.o_blue_card {
16-
background-color: $blue-300;
17-
}
12+
&.o_dashboard_card--active,&:hover, &:active {
13+
background-color: $purple-200;
14+
}
1815

19-
@media (max-width: 768px) {
20-
.o_website_sale_dashboard {
21-
flex-direction: column !important;
22-
align-items: center !important;
23-
justify-content: center !important;
24-
gap: 1.5rem !important;
16+
&.o_dashboard_card--active {
17+
border: 1px solid $purple-600;
18+
}
2519
}
2620

27-
/* Left Section: 3 cards in one row */
28-
.o_left_section {
29-
flex-direction: row !important;
30-
justify-content: center !important;
31-
flex-wrap: wrap !important;
32-
gap: 1rem !important;
33-
}
21+
&--orange.btn {
22+
background-color: $orange-100;
23+
color: $orange-600;
3424

35-
/* Right Section: DateFilterButton first, then metrics in one row */
36-
.o_right_section {
37-
flex-direction: column !important;
38-
align-items: center !important;
39-
justify-content: center !important;
40-
gap: 1rem !important;
41-
}
25+
&.o_dashboard_card--active, &:hover, &:active {
26+
background-color: $orange-200;
27+
}
4228

43-
.o_date_filter_wrapper {
44-
order: -1;
45-
justify-content: center !important;
46-
width: 100% !important;
47-
margin-bottom: 0.5rem !important;
29+
&.o_dashboard_card--active {
30+
border: 1px solid $orange-600;
31+
}
4832
}
4933

50-
.o_cards_container {
51-
flex-direction: row !important;
52-
justify-content: center !important;
53-
flex-wrap: wrap !important;
54-
gap: 0.8rem !important;
55-
}
34+
&--cyan.btn {
35+
background-color: $cyan-100;
36+
color: $cyan-600;
37+
38+
&.o_dashboard_card--active, &:hover, &:active {
39+
background-color: $cyan-200;
40+
}
5641

57-
/* Card Sizing */
58-
.o_dashboard_card {
59-
width: 90px !important;
60-
max-width: 130px !important;
61-
text-align: center !important;
42+
&.o_dashboard_card--active {
43+
border: 1px solid $cyan-600;
44+
}
6245
}
46+
}
6347

64-
/* Font Adjustments */
65-
.o_dashboard_card_data {
66-
font-size: 1.1rem !important;
48+
.o_website_sale_dashboard {
49+
background-color: $o-control-panel-background-color;
50+
}
51+
52+
html .o_web_client > .o_action_manager > [class*="o_website_sale_dashboard"] {
53+
.o_content {
54+
height: calc(100% - 4rem);
6755
}
6856

69-
.o_dashboard_card_text {
70-
margin-top: 4px;
71-
font-size: 0.7rem !important;
57+
.o_renderer {
58+
@include media-breakpoint-up(lg) {
59+
height: calc(100% - 6.75rem);
60+
min-height: calc(100% - 6.75rem);
61+
}
62+
63+
height: calc(100% - 6rem);
64+
min-height: calc(100% - 6rem);
65+
overflow: auto;
7266
}
7367
}

addons/website_sale/static/src/js/website_sale_dashboard/website_sale_dashboard.xml

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,86 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<templates>
33
<t t-name="website_sale.WebsiteSaleDashboard">
4-
<div class="o_website_sale_dashboard container-fluid d-flex flex-wrap justify-content-between align-items-start gap-3 p-3 border-bottom">
4+
<div class="o_website_sale_dashboard container-fluid d-flex justify-content-between align-items-center gap-3 p-3 border-bottom">
55

66
<!-- Left Section -->
7-
<div class="o_left_section align-content-center d-flex flex-wrap gap-4 flex-grow-1 justify-content-start">
7+
<div class="o_left_section d-flex flex-grow-1 justify-content-lg-start justify-content-md-center gap-1">
88
<div
99
title="Orders to Fulfill"
10-
class="o_dashboard_card o_purple_card text-center rounded-3 p-2 shadow-sm flex-shrink-1"
11-
t-on-click="setSearchContext"
10+
class="o_dashboard_card btn flex-grow-1 py-2 py-lg-3 text-center lh-sm"
11+
t-att-class="this.getDashboardCardAdditionalClass('to_fulfill')"
12+
t-on-click="state.eCommerceData['overall']['to_fulfill'] > 0 ? setSearchContext : () => {}"
1213
filter_name="to_fulfill,from_website,order_confirmed"
14+
role="button"
1315
>
14-
<a href="#" class="btn p-1 p-lg-2 text-truncate text-wrap">
15-
<h2 class="m-0 fw-bold o_dashboard_card_data" t-esc="state.eCommerceData['overall']['to_fulfill']"/>
16-
<h4 class="fw-semibold text-muted o_dashboard_card_text">To Fulfill</h4>
17-
</a>
16+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_fulfill']"/>
17+
<span class="o_dashboard_card_text d-block text-body">To Fulfill</span>
1818
</div>
1919

2020
<div
21+
t-if="state.eCommerceData['overall']['to_confirm'] > 0"
2122
title="Orders to Confirm"
22-
class="o_dashboard_card o_orange_card text-center rounded-3 p-2 shadow-sm flex-shrink-1"
23-
t-on-click="setSearchContext"
23+
class="o_dashboard_card btn flex-grow-1 py-2 py-lg-3 text-center lh-sm"
24+
t-att-class="this.getDashboardCardAdditionalClass('to_confirm')"
25+
t-on-click="state.eCommerceData['overall']['to_confirm'] > 0 ? setSearchContext : () => {}"
2426
filter_name="to_confirm,from_website"
27+
role="button"
2528
>
26-
<a href="#" class="btn p-1 p-lg-2 text-truncate text-wrap">
27-
<h2 class="m-0 fw-bold o_dashboard_card_data" t-esc="state.eCommerceData['overall']['to_confirm']"/>
28-
<h4 class="fw-semibold text-muted o_dashboard_card_text">To Confirm</h4>
29-
</a>
29+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_confirm']"/>
30+
<span class="o_dashboard_card_text d-block text-body">To Confirm</span>
3031
</div>
3132

3233
<div
3334
title="Orders to Invoice"
34-
class="o_dashboard_card o_blue_card text-center rounded-3 p-2 shadow-sm flex-shrink-1"
35-
t-on-click="setSearchContext"
35+
class="o_dashboard_card btn flex-grow-1 py-2 py-lg-3 text-center lh-sm"
36+
t-att-class="this.getDashboardCardAdditionalClass('to_invoice')"
37+
t-on-click="state.eCommerceData['overall']['to_invoice'] > 0 ? setSearchContext : () => {}"
3638
filter_name="to_invoice,from_website,order_confirmed"
39+
role="button"
3740
>
38-
<a href="#" class="btn p-1 p-lg-2 text-truncate text-wrap">
39-
<h2 class="m-0 fw-bold o_dashboard_card_data" t-esc="state.eCommerceData['overall']['to_invoice']"/>
40-
<h4 class="fw-semibold text-muted o_dashboard_card_text">To Invoice</h4>
41-
</a>
41+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_invoice']"/>
42+
<span class="o_dashboard_card_text d-block text-body">To Invoice</span>
4243
</div>
4344
</div>
4445

4546
<!-- Right Section -->
46-
<div class="o_right_section align-content-center d-flex flex-wrap gap-4 flex-grow-1 justify-content-end">
47-
<div class="o_date_filter_wrapper d-flex align-items-center w-100 w-md-auto mb-3 mb-md-0">
48-
<DateFilterButton update.bind="updateDashboardState" selectedFilter="state.selectedFilter"/>
47+
<div class="d-none d-lg-flex border rounded-3 py-3">
48+
<div class="o_date_filter_wrapper d-flex align-items-center">
49+
<DateFilterButton update.bind="updateDashboardState" selectedDateFilter="state.selectedDateFilter"/>
4950
</div>
5051

51-
<div class="o_cards_container d-flex flex-wrap justify-content-end gap-4">
52+
<div class="o_cards_container d-flex align-items-center border-start gap-4 px-4">
5253
<!-- Sales -->
53-
<div class="o_dashboard_card o_visitors_card text-center bg-secondary rounded-3 p-3 shadow-sm flex-shrink-1">
54-
<h3
55-
class="m-0 fw-bold o_dashboard_card_data"
54+
<div class="d-flex align-items-baseline gap-1">
55+
<span
56+
class="o_dashboard_card_data fs-5 fw-bolder"
5657
t-att-class="this.getPeriodCardClass('total_visitors')"
5758
>
5859
<t t-esc="state.eCommerceData['current_period']['total_visitors']"/>
59-
</h3>
60-
<div class="fw-semibold text-muted o_dashboard_card_text">Visitors</div>
60+
</span>
61+
<span class="o_dashboard_card_text fs-6 text-body fw-bold">Visitors</span>
6162
</div>
6263

6364
<!-- Conversion -->
64-
<div class="o_dashboard_card o_orders_card text-center bg-secondary rounded-3 p-3 shadow-sm flex-shrink-1">
65-
<h3
66-
class="m-0 fw-bold o_dashboard_card_data"
65+
<div class="d-flex align-items-baseline gap-1">
66+
<span
67+
class="o_dashboard_card_data fs-5 fw-bolder"
6768
t-att-class="this.getPeriodCardClass('total_orders')"
6869
>
6970
<t t-esc="state.eCommerceData['current_period']['total_orders']"/>
70-
</h3>
71-
<div class="fw-semibold text-muted o_dashboard_card_text">Orders</div>
71+
</span>
72+
<span class="o_dashboard_card_text fs-6 text-body fw-bold">Orders</span>
7273
</div>
7374

7475
<!-- Average Cart -->
75-
<div class="o_dashboard_card o_sales_card text-center bg-secondary rounded-3 p-3 shadow-sm flex-shrink-1">
76-
<h3
77-
class="m-0 fw-bold o_dashboard_card_data"
76+
<div class="d-flex align-items-baseline gap-1">
77+
<span
78+
class="o_dashboard_card_data fs-5 fw-bolder"
7879
t-att-class="this.getPeriodCardClass('total_sales')"
7980
>
8081
$ <t t-esc="state.eCommerceData['current_period']['total_sales']"/>
81-
</h3>
82-
<div class="fw-semibold text-muted o_dashboard_card_text">Sales</div>
82+
</span>
83+
<span class="o_dashboard_card_text fs-6 text-body fw-bold">Sales</span>
8384
</div>
8485
</div>
8586
</div>

0 commit comments

Comments
 (0)