From f113354cb826158366c8ed4ae857118cdcaae77f Mon Sep 17 00:00:00 2001 From: Jonathan Adeline <jonathan@baserow.io> Date: Mon, 10 Mar 2025 05:50:54 +0000 Subject: [PATCH] Enhance dashboard charts design --- .../assets/scss/components/chart.scss | 22 +++++ .../components/dashboard_chart_widget.scss | 14 +++- .../dashboard/components/widget/Chart.vue | 79 +++++++++++++++++- .../components/widget/ChartWidget.vue | 80 ++++++++++++------- .../assets/scss/components/dashboard/all.scss | 2 +- .../dashboard/dashboard_summary_widget.scss | 11 ++- .../scss/components/dashboard/widget.scss | 62 ++++++++++++++ .../components/dashboard/widget_header.scss | 57 ------------- .../components/widget/DashboardWidget.vue | 6 +- .../components/widget/SummaryWidget.vue | 80 ++++++++++--------- .../components/widget/WidgetContextMenu.vue | 4 +- 11 files changed, 279 insertions(+), 138 deletions(-) create mode 100644 web-frontend/modules/core/assets/scss/components/dashboard/widget.scss delete mode 100644 web-frontend/modules/core/assets/scss/components/dashboard/widget_header.scss diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/chart.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/chart.scss index 172a17c31..6de5bd78a 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/chart.scss +++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/chart.scss @@ -1,3 +1,25 @@ .chart { max-height: 320px; } + +.chart__no-data { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + padding: 10px 0; + flex-direction: column; + gap: 36px; +} + +.chart__no-data-dashed-line { + border-top: 1px dashed $palette-neutral-200; + width: 100%; + height: 1px; +} + +.chart__no-data-plain-line { + border-top: 1px solid $palette-neutral-200; + width: 100%; + height: 1px; +} diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/dashboard_chart_widget.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/dashboard_chart_widget.scss index 254e3bd9b..6a3a4a9fb 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/dashboard_chart_widget.scss +++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/dashboard_chart_widget.scss @@ -1,3 +1,15 @@ .dashboard-chart-widget { - padding: 0 24px 24px; + // nothing +} + +.dashboard-chart-widget__content { + height: 280px; +} + +.dashboard-chart-widget__loading { + height: 341px; + + .dashboard-chart-widget--with-header-description & { + height: 362.8px; + } } diff --git a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/Chart.vue b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/Chart.vue index 0c99a92bf..22b194a05 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/Chart.vue +++ b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/Chart.vue @@ -1,5 +1,20 @@ <template> - <Bar id="chart-id" :options="chartOptions" :data="chartData" class="chart" /> + <Bar + v-if="chartData.datasets.length > 0" + id="chart-id" + :options="chartOptions" + :data="chartData" + class="chart" + /> + + <div v-else class="chart__no-data"> + <span class="chart__no-data-dashed-line"></span> + <span class="chart__no-data-dashed-line"></span> + <span class="chart__no-data-dashed-line"></span> + <span class="chart__no-data-dashed-line"></span> + <span class="chart__no-data-dashed-line"></span> + <span class="chart__no-data-plain-line"></span> + </div> </template> <script> @@ -50,6 +65,36 @@ export default { display: true, align: 'start', position: 'bottom', + labels: { + usePointStyle: true, + boxWidth: 14, + pointStyle: 'circle', + padding: 20, + }, + }, + tooltip: { + backgroundColor: '#202128', + padding: 10, + bodyFont: { + size: 12, + }, + titleFont: { + size: 12, + }, + }, + }, + elements: { + bar: { + borderRadius: { + topLeft: 4, + topRight: 4, + bottomLeft: 0, + bottomRight: 0, + }, + borderWidth: 1, + borderColor: '#5190ef', + backgroundColor: '#5190ef', + hoverBackgroundColor: '#5190ef', }, }, } @@ -88,7 +133,7 @@ export default { datasets.push({ data: seriesData, label, - backgroundColor: this.chartColors[index], + ...this.chartColors[index], }) } return { @@ -107,7 +152,7 @@ export default { datasets.push({ data: seriesData, label, - backgroundColor: this.chartColors[index], + ...this.chartColors[index], }) } return { @@ -124,7 +169,33 @@ export default { }) }, chartColors() { - return ['#4E5CFE', '#2BC3F1', '#FFC744', '#E26AB0', '#3E4ACB'] + return [ + { + backgroundColor: '#5190ef', + borderColor: '#5190ef', + hoverBackgroundColor: '#5190ef', + }, + { + backgroundColor: '#2BC3F1', + borderColor: '#2BC3F1', + hoverBackgroundColor: '#2BC3F1', + }, + { + backgroundColor: '#FFC744', + borderColor: '#FFC744', + hoverBackgroundColor: '#FFC744', + }, + { + backgroundColor: '#E26AB0', + borderColor: '#E26AB0', + hoverBackgroundColor: '#E26AB0', + }, + { + backgroundColor: '#3E4ACB', + borderColor: '#3E4ACB', + hoverBackgroundColor: '#3E4ACB', + }, + ] }, }, methods: { diff --git a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/ChartWidget.vue b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/ChartWidget.vue index 21f26ab60..152c85f44 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/ChartWidget.vue +++ b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/widget/ChartWidget.vue @@ -1,38 +1,48 @@ <template> - <div class="dashboard-chart-widget"> - <div class="widget-header"> - <div class="widget-header__main"> - <div class="widget-header__title-wrapper"> - <div class="widget-header__title">{{ widget.title }}</div> - <div - v-if="dataSourceMisconfigured" - class="widget-header__fix-configuration" - > - <svg - width="5" - height="6" - viewBox="0 0 5 6" - fill="none" - xmlns="http://www.w3.org/2000/svg" + <div + class="dashboard-chart-widget" + :class="{ + 'dashboard-chart-widget--with-header-description': widget.description, + }" + > + <template v-if="!loading"> + <div + class="widget__header" + :class="{ + 'widget__header--edit-mode': editMode, + }" + > + <div class="widget__header-main"> + <div class="widget__header-title-wrapper"> + <div class="widget__header-title">{{ widget.title }}</div> + + <Badge + v-if="dataSourceMisconfigured" + color="red" + size="small" + indicator + rounded + >{{ $t('widget.fixConfiguration') }}</Badge > - <circle cx="2.5" cy="3" r="2.5" fill="#FF5A44" /> - </svg> - {{ $t('widget.fixConfiguration') }} + </div> + <div v-if="widget.description" class="widget__header-description"> + {{ widget.description }} </div> </div> - <div v-if="widget.description" class="widget-header__description"> - {{ widget.description }} - </div> + <WidgetContextMenu + v-if="isEditMode" + :widget="widget" + :dashboard="dashboard" + @delete-widget="$emit('delete-widget', $event)" + ></WidgetContextMenu> </div> - <WidgetContextMenu - v-if="isEditMode" - :widget="widget" - :dashboard="dashboard" - @delete-widget="$emit('delete-widget', $event)" - ></WidgetContextMenu> - </div> - <Chart :data-source="dataSource" :data-source-data="dataForDataSource"> - </Chart> + + <div class="dashboard-chart-widget__content widget__content"> + <Chart :data-source="dataSource" :data-source-data="dataForDataSource"> + </Chart> + </div> + </template> + <div v-else class="dashboard-chart-widget__loading loading-spinner"></div> </div> </template> @@ -57,6 +67,16 @@ export default { required: false, default: '', }, + loading: { + type: Boolean, + required: false, + default: false, + }, + editMode: { + type: Boolean, + required: false, + default: false, + }, }, computed: { dataSource() { diff --git a/web-frontend/modules/core/assets/scss/components/dashboard/all.scss b/web-frontend/modules/core/assets/scss/components/dashboard/all.scss index c19a721f5..773991c1a 100644 --- a/web-frontend/modules/core/assets/scss/components/dashboard/all.scss +++ b/web-frontend/modules/core/assets/scss/components/dashboard/all.scss @@ -7,6 +7,6 @@ @import 'dashboard_widget'; @import 'dashboard_summary_widget'; @import 'create_widget_button'; -@import 'widget_header'; +@import 'widget'; @import 'widget_settings_base_form'; @import 'create_widget_modal'; diff --git a/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_summary_widget.scss b/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_summary_widget.scss index 20283267c..591f1c3f1 100644 --- a/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_summary_widget.scss +++ b/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_summary_widget.scss @@ -1,5 +1,5 @@ .dashboard-summary-widget { - padding: 0 24px 24px; + // nothing } .dashboard-summary-widget__summary { @@ -7,7 +7,6 @@ font-size: 40px; font-weight: 600; line-height: 40px; - margin-top: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -16,3 +15,11 @@ .dashboard-summary-widget__summary--misconfigured { color: #cdcecf; } + +.dashboard-summary-widget__loading { + height: 120px; + + .dashboard-summary-widget--with-header-description & { + height: 141.8px; + } +} diff --git a/web-frontend/modules/core/assets/scss/components/dashboard/widget.scss b/web-frontend/modules/core/assets/scss/components/dashboard/widget.scss new file mode 100644 index 000000000..91730655c --- /dev/null +++ b/web-frontend/modules/core/assets/scss/components/dashboard/widget.scss @@ -0,0 +1,62 @@ +.widget__header { + display: flex; + flex-direction: row; + align-items: start; + gap: 7px; + padding: 20px 24px; + border-bottom: 1px solid $palette-neutral-200; + position: relative; + height: auto; + + &--no-border { + border-bottom: none; + padding-bottom: 0; + } + + &--edit-mode { + padding: 20px 55px 20px 24px; + } +} + +.widget__header-main { + display: flex; + flex-direction: column; + min-width: 0; +} + +.widget__header-context-menu { + margin-left: auto; + width: 46px; + position: absolute; + right: 0; + top: 8px; +} + +.widget__header-title-wrapper { + display: flex; + align-items: center; + gap: 14px; + height: 20px; +} + +.widget__header-title { + color: $palette-neutral-1200; + font-size: 16px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.widget__header-description { + @extend %ellipsis; + + color: #6a6b70; + font-size: 12px; + font-weight: 400; + margin-top: 8px; +} + +.widget__content { + padding: 20px; +} diff --git a/web-frontend/modules/core/assets/scss/components/dashboard/widget_header.scss b/web-frontend/modules/core/assets/scss/components/dashboard/widget_header.scss deleted file mode 100644 index f8b2d55ae..000000000 --- a/web-frontend/modules/core/assets/scss/components/dashboard/widget_header.scss +++ /dev/null @@ -1,57 +0,0 @@ -.widget-header { - display: flex; - flex-direction: row; - align-items: start; - gap: 7px; -} - -.widget-header__main { - padding-top: 24px; - overflow: hidden; -} - -.widget-header__context-menu { - flex-grow: 1; - text-align: right; - margin: 6px -14px 0 7px; - width: 46px; -} - -.widget-header__title-wrapper { - display: flex; - gap: 7px; -} - -.widget-header__title { - color: $palette-neutral-1200; - font-size: 16px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.widget-header__description { - color: #6a6b70; - font-size: 12px; - font-weight: 400; - margin-top: 8px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.widget-header__fix-configuration { - display: flex; - align-items: center; - gap: 6px; - color: #b23f30; - font-size: 10px; - font-weight: 500; - line-height: 12px; - letter-spacing: 0.2px; - padding: 4px 8px; - border-radius: 80px; - background: #fff2f0; - white-space: nowrap; -} diff --git a/web-frontend/modules/dashboard/components/widget/DashboardWidget.vue b/web-frontend/modules/dashboard/components/widget/DashboardWidget.vue index f0bd7d66b..ab305bab3 100644 --- a/web-frontend/modules/dashboard/components/widget/DashboardWidget.vue +++ b/web-frontend/modules/dashboard/components/widget/DashboardWidget.vue @@ -12,14 +12,12 @@ </div> <component :is="widgetComponent(widget.type)" - v-if="isLoading === false" :dashboard="dashboard" :widget="widget" :store-prefix="storePrefix" + :loading="isLoading" + :edit-mode="isEditMode" /> - <div v-else> - <div class="dashboard-widget__loading"></div> - </div> </div> </template> diff --git a/web-frontend/modules/dashboard/components/widget/SummaryWidget.vue b/web-frontend/modules/dashboard/components/widget/SummaryWidget.vue index 3ff83e494..9d17efbc9 100644 --- a/web-frontend/modules/dashboard/components/widget/SummaryWidget.vue +++ b/web-frontend/modules/dashboard/components/widget/SummaryWidget.vue @@ -1,45 +1,46 @@ <template> - <div class="dashboard-summary-widget"> - <div class="widget-header"> - <div class="widget-header__main"> - <div class="widget-header__title-wrapper"> - <div class="widget-header__title">{{ widget.title }}</div> - <div - v-if="dataSourceMisconfigured" - class="widget-header__fix-configuration" - > - <svg - width="5" - height="6" - viewBox="0 0 5 6" - fill="none" - xmlns="http://www.w3.org/2000/svg" + <div + class="dashboard-summary-widget" + :class="{ + 'dashboard-summary-widget--with-header-description': widget.description, + }" + > + <template v-if="!loading"> + <div class="widget__header widget__header--no-border"> + <div class="widget__header-main"> + <div class="widget__header-title-wrapper"> + <div class="widget__header-title">{{ widget.title }}</div> + + <Badge + v-if="dataSourceMisconfigured" + color="red" + indicator + rounded + >{{ $t('widget.fixConfiguration') }}</Badge > - <circle cx="2.5" cy="3" r="2.5" fill="#FF5A44" /> - </svg> - {{ $t('widget.fixConfiguration') }} + </div> + <div v-if="widget.description" class="widget__header-description"> + {{ widget.description }} </div> </div> - <div v-if="widget.description" class="widget-header__description"> - {{ widget.description }} - </div> + <WidgetContextMenu + v-if="isEditMode" + :widget="widget" + :dashboard="dashboard" + @delete-widget="$emit('delete-widget', $event)" + ></WidgetContextMenu> </div> - <WidgetContextMenu - v-if="isEditMode" - :widget="widget" - :dashboard="dashboard" - @delete-widget="$emit('delete-widget', $event)" - ></WidgetContextMenu> - </div> - <div - class="dashboard-summary-widget__summary" - :class="{ - 'dashboard-summary-widget__summary--misconfigured': - dataSourceMisconfigured, - }" - > - {{ result }} - </div> + <div + class="widget__content dashboard-summary-widget__summary" + :class="{ + 'dashboard-summary-widget__summary--misconfigured': + dataSourceMisconfigured, + }" + > + {{ result }} + </div> + </template> + <div v-else class="dashboard-summary-widget__loading loading-spinner"></div> </div> </template> @@ -63,6 +64,11 @@ export default { required: false, default: '', }, + loading: { + type: Boolean, + required: false, + default: false, + }, }, computed: { dataSource() { diff --git a/web-frontend/modules/dashboard/components/widget/WidgetContextMenu.vue b/web-frontend/modules/dashboard/components/widget/WidgetContextMenu.vue index a465ed5e3..38b191d1c 100644 --- a/web-frontend/modules/dashboard/components/widget/WidgetContextMenu.vue +++ b/web-frontend/modules/dashboard/components/widget/WidgetContextMenu.vue @@ -1,11 +1,11 @@ <template> - <div ref="contextButton" class="widget-header__context-menu"> + <div ref="contextButton" class="widget__header-context-menu"> <ButtonIcon icon="iconoir-more-vert" type="secondary" size="regular" @click.stop=" - $refs.context.toggle($refs.contextButton, 'bottom', 'right', 8, 0) + $refs.context.toggle($refs.contextButton, 'bottom', 'right', 8, -8) " ></ButtonIcon> <WidgetContext