Skip to content

Commit fb510a9

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 fb510a9

File tree

7 files changed

+145
-118
lines changed

7 files changed

+145
-118
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="border-0 px-4 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.o_dashboard_card {
2+
--DashboardCard__purple-bg-color: #{$purple-200};
3+
--DashboardCard__purple-bg-color--active: #{$purple-300};
4+
--DashboardCard__orange-bg-color: #{$orange-200};
5+
--DashboardCard__orange-bg-color--active: #{$orange-300};
6+
--DashboardCard__cyan-bg-color: #{$cyan-200};
7+
--DashboardCard__cyan-bg-color--active: #{$cyan-300};
8+
}

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('active');
97+
}
98+
return dashboardCardClasses.join(' ');
6399
}
64100
}
Lines changed: 43 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,53 @@
11
.o_dashboard_card {
2-
min-width: 120px;
3-
min-height: 90px;
4-
align-content: center;
5-
}
6-
7-
.o_purple_card {
8-
background-color: $purple-300;
9-
}
10-
11-
.o_orange_card {
12-
background-color: $orange-300;
2+
$dashboard-colors: (
3+
purple: (
4+
bg: var(--DashboardCard__purple-bg-color, #{$purple-100}),
5+
bg-active: var(--DashboardCard__purple-bg-color--active, #{$purple-200}),
6+
color: var(--DashboardCard__purple-color, #{$purple-600}),
7+
),
8+
cyan: (
9+
bg: var(--DashboardCard__cyan-bg-color, #{$cyan-100}),
10+
bg-active: var(--DashboardCard__cyan-bg-color--active, #{$cyan-200}),
11+
color: var(--DashboardCard__cyan-color, #{$cyan-600}),
12+
),
13+
orange: (
14+
bg: var(--DashboardCard__orange-bg-color, #{$orange-100}),
15+
bg-active: var(--DashboardCard__orange-bg-color--active, #{$orange-200}),
16+
color: var(--DashboardCard__orange-color, #{$orange-600}),
17+
),
18+
);
19+
20+
@each $name, $states in $dashboard-colors {
21+
&.o_dashboard_card_#{$name} {
22+
--btn-bg: #{map-get($states, bg)};
23+
--btn-hover-bg: #{map-get($states, bg-active)};
24+
--btn-active-bg: #{map-get($states, bg-active)};
25+
--btn-active-border-color: #{map-get($states, color)};
26+
27+
.o_dashboard_card_data {
28+
color: #{map-get($states, color)};
29+
}
30+
}
31+
}
1332
}
1433

15-
.o_blue_card {
16-
background-color: $blue-300;
34+
.o_website_sale_dashboard {
35+
background-color: $o-control-panel-background-color;
1736
}
1837

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;
25-
}
26-
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-
}
34-
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-
}
42-
43-
.o_date_filter_wrapper {
44-
order: -1;
45-
justify-content: center !important;
46-
width: 100% !important;
47-
margin-bottom: 0.5rem !important;
48-
}
49-
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-
}
56-
57-
/* Card Sizing */
58-
.o_dashboard_card {
59-
width: 90px !important;
60-
max-width: 130px !important;
61-
text-align: center !important;
38+
html .o_web_client > .o_action_manager > [class*="o_website_sale_dashboard"] {
39+
.o_content {
40+
height: calc(100% - 4rem);
6241
}
6342

64-
/* Font Adjustments */
65-
.o_dashboard_card_data {
66-
font-size: 1.1rem !important;
67-
}
43+
.o_renderer {
44+
@include media-breakpoint-up(lg) {
45+
height: calc(100% - 6.75rem);
46+
min-height: calc(100% - 6.75rem);
47+
}
6848

69-
.o_dashboard_card_text {
70-
margin-top: 4px;
71-
font-size: 0.7rem !important;
49+
height: calc(100% - 6rem);
50+
min-height: calc(100% - 6rem);
51+
overflow: auto;
7252
}
7353
}

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

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,83 @@
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">
8-
<div
7+
<div class="grid w-100 w-lg-50 gap-1">
8+
<button
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 g-col-4 g-col-md-2 g-col-lg-3 g-col-xxl-2 g-start-md-4 g-start-lg-1 py-2 py-lg-3 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"
1314
>
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>
18-
</div>
15+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_fulfill']"/>
16+
<span class="d-block">To Fulfill</span>
17+
</button>
1918

20-
<div
19+
<button
20+
t-if="state.eCommerceData['overall']['to_confirm'] > 0"
2121
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"
22+
class="o_dashboard_card btn g-col-4 g-col-md-2 g-col-lg-3 g-col-xxl-2 py-2 py-lg-3 lh-sm"
23+
t-att-class="this.getDashboardCardAdditionalClass('to_confirm')"
24+
t-on-click="state.eCommerceData['overall']['to_confirm'] > 0 ? setSearchContext : () => {}"
2425
filter_name="to_confirm,from_website"
2526
>
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>
30-
</div>
27+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_confirm']"/>
28+
<span class="d-block">To Confirm</span>
29+
</button>
3130

32-
<div
31+
<button
3332
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"
33+
class="o_dashboard_card btn g-col-4 g-col-md-2 g-col-lg-3 g-col-xxl-2 py-2 py-lg-3 lh-sm"
34+
t-att-class="this.getDashboardCardAdditionalClass('to_invoice')"
35+
t-on-click="state.eCommerceData['overall']['to_invoice'] > 0 ? setSearchContext : () => {}"
3636
filter_name="to_invoice,from_website,order_confirmed"
3737
>
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>
42-
</div>
38+
<span class="o_dashboard_card_data fs-4 fw-bolder" t-esc="state.eCommerceData['overall']['to_invoice']"/>
39+
<span class="d-block">To Invoice</span>
40+
</button>
4341
</div>
4442

4543
<!-- 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"/>
44+
<div class="d-none d-lg-flex border rounded-3 py-3">
45+
<div class="o_date_filter_wrapper d-flex align-items-center">
46+
<DateFilterButton update.bind="updateDashboardState" selectedDateFilter="state.selectedDateFilter"/>
4947
</div>
5048

51-
<div class="o_cards_container d-flex flex-wrap justify-content-end gap-4">
49+
<div class="o_cards_container d-flex align-items-center gap-4 border-start px-4">
5250
<!-- 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"
51+
<div class="d-flex align-items-baseline gap-1">
52+
<span
53+
class="fs-5 fw-bolder"
5654
t-att-class="this.getPeriodCardClass('total_visitors')"
5755
>
5856
<t t-esc="state.eCommerceData['current_period']['total_visitors']"/>
59-
</h3>
60-
<div class="fw-semibold text-muted o_dashboard_card_text">Visitors</div>
57+
</span>
58+
<span class="fs-6 text-body fw-bold">Visitors</span>
6159
</div>
6260

6361
<!-- 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"
62+
<div class="d-flex align-items-baseline gap-1">
63+
<span
64+
class="fs-5 fw-bolder"
6765
t-att-class="this.getPeriodCardClass('total_orders')"
6866
>
6967
<t t-esc="state.eCommerceData['current_period']['total_orders']"/>
70-
</h3>
71-
<div class="fw-semibold text-muted o_dashboard_card_text">Orders</div>
68+
</span>
69+
<span class="fs-6 text-body fw-bold">Orders</span>
7270
</div>
7371

7472
<!-- 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"
73+
<div class="d-flex align-items-baseline gap-1">
74+
<span
75+
class="fs-5 fw-bolder"
7876
t-att-class="this.getPeriodCardClass('total_sales')"
7977
>
8078
$ <t t-esc="state.eCommerceData['current_period']['total_sales']"/>
81-
</h3>
82-
<div class="fw-semibold text-muted o_dashboard_card_text">Sales</div>
79+
</span>
80+
<span class="fs-6 text-body fw-bold">Sales</span>
8381
</div>
8482
</div>
8583
</div>

0 commit comments

Comments
 (0)