Initial commit
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<section class="catalog">
|
||||
<div class="container grid">
|
||||
<div class="full-width">
|
||||
<SectionTitle class="grid-cell">
|
||||
<template slot="icon">
|
||||
<img src="../../assets/icons/section-title/catalog.svg" width="18" height="20" />
|
||||
</template>
|
||||
<template slot="title">
|
||||
<span>{{ $t('CatalogPage.header') }}</span>
|
||||
</template>
|
||||
</SectionTitle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="catalog__filter container grid" action="">
|
||||
<div class="grid-cell grid-cell--col6 catalog__filter--dropdown">
|
||||
<CatalogDropdown ref="dropdownCategories"
|
||||
:title="$t('CatalogDropdown.category.title')"
|
||||
:placeholder="$t('CatalogDropdown.category.placeholder')"
|
||||
:options="categories"
|
||||
:disabled="true"
|
||||
@selected="getCountries"
|
||||
@reset="resetCategory">
|
||||
<template slot="extra">
|
||||
<span v-html="$t('CatalogDropdown.category.extra')" class="text is-small"></span>
|
||||
</template>
|
||||
</CatalogDropdown>
|
||||
</div>
|
||||
<div class="grid-cell grid-cell--col6 catalog__filter--dropdown">
|
||||
<CatalogDropdown ref="dropdownCountries"
|
||||
:title="$t('CatalogDropdown.country.title')"
|
||||
:placeholder="$t('CatalogDropdown.country.placeholder')"
|
||||
:options="countries"
|
||||
:disabled="true"
|
||||
:limitHeight="true"
|
||||
@open="false"
|
||||
@selected="getDatasets"
|
||||
@reset="resetCountry">
|
||||
<template slot="extra">
|
||||
<span v-html="$t('CatalogDropdown.country.extra')" class="text is-small"></span>
|
||||
</template>
|
||||
</CatalogDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datasets list -->
|
||||
<div class="container grid" v-if="datasets.length > 0">
|
||||
<div class="grid-cell grid-cell--col12">
|
||||
<CatalogListHeader :order="appliedOrder" :orderDirection="appliedOrderDirection" @changeOrder="applyOrder"></CatalogListHeader>
|
||||
<ul v-if="!isFetchingDatasets" >
|
||||
<li v-for="(dataset) in currentPageDatasets" :key="dataset.id">
|
||||
<CatalogCard :dataset="dataset"></CatalogCard>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul v-else>
|
||||
<li v-for="(n) in resultsPerPage" :key="n">
|
||||
<CatalogFakeCard />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination v-if="shouldShowPagination" :page=currentPage :numPages=numPages @pageChange="goToPage"></Pagination>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import sendCustomEvent from 'new-dashboard/utils/send-custom-event';
|
||||
import SectionTitle from 'new-dashboard/components/SectionTitle';
|
||||
import CatalogListHeader from 'new-dashboard/components/Catalog/CatalogListHeader';
|
||||
import CatalogCard from 'new-dashboard/components/Catalog/CatalogCard';
|
||||
import CatalogFakeCard from 'new-dashboard/components/Catalog/CatalogFakeCard';
|
||||
import CatalogDropdown from 'new-dashboard/components/Catalog/CatalogDropdown';
|
||||
import Pagination from 'new-dashboard/components/Pagination';
|
||||
|
||||
export default {
|
||||
name: 'CatalogPage',
|
||||
components: {
|
||||
SectionTitle,
|
||||
CatalogListHeader,
|
||||
CatalogCard,
|
||||
CatalogFakeCard,
|
||||
CatalogDropdown,
|
||||
Pagination
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
sendCustomEvent('catalogSelectCategory', {
|
||||
catalogSelectedCategory: to.query.category
|
||||
});
|
||||
sendCustomEvent('catalogSelectCountry', {
|
||||
catalogSelectedCountry: to.query.country
|
||||
});
|
||||
next();
|
||||
},
|
||||
mounted () {
|
||||
const category = this.$route.query.category;
|
||||
const country = this.$route.query.country;
|
||||
if (category && country) {
|
||||
this.setUrlOptions();
|
||||
} else {
|
||||
this.getCategories();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
numPages: state => state.catalog.numPages,
|
||||
currentPage: state => state.catalog.page,
|
||||
datasets: state => state.catalog.list,
|
||||
categories: state => state.catalog.categories,
|
||||
countries: state => state.catalog.countries,
|
||||
isFetchingDatasets: state => state.catalog.isFetching,
|
||||
numResults: state => state.catalog.umResults,
|
||||
resultsPerPage: state => state.catalog.resultsPerPage,
|
||||
appliedOrder: state => state.catalog.order,
|
||||
appliedOrderDirection: state => state.catalog.orderDirection
|
||||
}),
|
||||
shouldShowPagination () {
|
||||
return !this.isFetchingDatasets && this.numPages > 1;
|
||||
},
|
||||
currentPageDatasets () {
|
||||
return this.datasets.slice(((this.currentPage - 1) * this.resultsPerPage), (this.currentPage * this.resultsPerPage));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToPage (page) {
|
||||
window.scroll({ top: 0, left: 0 });
|
||||
this.$router.push({
|
||||
name: 'catalog',
|
||||
params: this.$route.params,
|
||||
query: { ...this.$route.query, page }
|
||||
});
|
||||
},
|
||||
applyOrder (orderParams) {
|
||||
this.$router.push({
|
||||
name: 'catalog',
|
||||
params: this.$route.params,
|
||||
query: {
|
||||
...this.$route.query,
|
||||
page: 1,
|
||||
order: orderParams.order,
|
||||
order_direction: orderParams.direction
|
||||
}
|
||||
});
|
||||
this.$store.dispatch('catalog/order', orderParams);
|
||||
},
|
||||
getCategories () {
|
||||
this.clearList();
|
||||
this.$store.dispatch('catalog/fetchCategories')
|
||||
.then(
|
||||
() => {
|
||||
this.$refs.dropdownCategories.enableDropdown();
|
||||
this.$refs.dropdownCategories.openDropdown();
|
||||
},
|
||||
(err) => {
|
||||
this.$refs.dropdownCategories.enableDropdown();
|
||||
this.$refs.dropdownCategories.setError(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
getCountries (category) {
|
||||
this.clearList();
|
||||
this.$store.dispatch('catalog/fetchCountries', category)
|
||||
.then(
|
||||
() => {
|
||||
this.$refs.dropdownCountries.clearInput();
|
||||
this.$refs.dropdownCountries.enableDropdown();
|
||||
window.scroll({ top: 0, left: 0 });
|
||||
},
|
||||
(err) => {
|
||||
this.$refs.dropdownCountries.enableDropdown();
|
||||
this.$refs.dropdownCountries.setError(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
getDatasets (country) {
|
||||
this.$store.dispatch('catalog/fetchDatasets', {
|
||||
category: this.$refs.dropdownCategories.selected,
|
||||
country
|
||||
}).then(
|
||||
() => {
|
||||
this.$router.push({
|
||||
name: 'catalog',
|
||||
query: {
|
||||
category: this.$refs.dropdownCategories.selected,
|
||||
country
|
||||
}
|
||||
});
|
||||
window.scroll({ top: 0, left: 0 });
|
||||
},
|
||||
(err) => {
|
||||
this.$refs.dropdownCategories.setError(err);
|
||||
this.$refs.dropdownCountries.setError(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
resetCategory () {
|
||||
this.$refs.dropdownCountries.clearInput();
|
||||
this.$refs.dropdownCountries.disableDropdown();
|
||||
this.clearList();
|
||||
},
|
||||
resetCountry () {
|
||||
this.clearList();
|
||||
},
|
||||
clearList () {
|
||||
this.$store.dispatch('catalog/clearList');
|
||||
},
|
||||
setUrlOptions () {
|
||||
const category = this.$route.query.category;
|
||||
const country = this.$route.query.country;
|
||||
const promiseDatasets = this.$store.dispatch('catalog/fetchDatasets', {
|
||||
category: category,
|
||||
country: country
|
||||
});
|
||||
const promiseCategories = this.$store.dispatch('catalog/fetchCategories');
|
||||
const promiseCountires = this.$store.dispatch('catalog/fetchCountries', category);
|
||||
Promise.all([promiseDatasets, promiseCategories, promiseCountires])
|
||||
.then(
|
||||
() => {
|
||||
if (this.datasets.length > 0) {
|
||||
this.$refs.dropdownCategories.setInput(category);
|
||||
this.$refs.dropdownCountries.setInput(country);
|
||||
} else {
|
||||
this.getCategories();
|
||||
this.$router.push({ name: 'catalog' });
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.$router.push({ name: 'catalog' });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'new-dashboard/styles/variables';
|
||||
|
||||
.catalog {
|
||||
min-height: 640px;
|
||||
margin-top: 64px;
|
||||
|
||||
&__filter {
|
||||
justify-content: space-between;
|
||||
height: 168px;
|
||||
|
||||
&--dropdown {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<Page class="page--data">
|
||||
<SecondaryNavigation>
|
||||
<a class="catalogDetail__back title is-small" href="javascript:history.back()">
|
||||
<img class="catalogDetail__back--icon" svg-inline src="../../assets/icons/common/back.svg"/>
|
||||
<span>{{ $t('CatalogDetailPage.back') }}</span>
|
||||
</a>
|
||||
</SecondaryNavigation>
|
||||
|
||||
<section v-if="!isFetchingDatasets" class="catalogDetail">
|
||||
<div class="container grid">
|
||||
<div class="full-width">
|
||||
<SectionTitle class="grid-cell">
|
||||
<template slot="icon">
|
||||
<img src="../../assets/icons/section-title/data.svg" width="18" height="20" />
|
||||
</template>
|
||||
<template slot="title">
|
||||
<span>{{ dataset.name }}</span>
|
||||
</template>
|
||||
|
||||
<template slot="actionButton">
|
||||
<CatalogRequestSuccess v-if="hasBeenSuccesfullyRequested"></CatalogRequestSuccess>
|
||||
<button v-else class="button is-primary" @click="requestDataset">{{ $t('CatalogDetailPage.requestDataset') }}</button>
|
||||
</template>
|
||||
</SectionTitle>
|
||||
|
||||
<div class="catalogDetail__description grid-cell">
|
||||
<span class="text is-caption">
|
||||
{{ dataset.description }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-cell grid-cell--col6">
|
||||
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.variableGroups') }}</h4>
|
||||
<ul v-for="variable in dataset.variable_name" :key="variable" class="catalogDetail__list">
|
||||
<li class="text is-caption catalogDetail__list--item">{{variable}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-cell grid-cell--col3">
|
||||
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.category') }}</h4>
|
||||
<div class="u-flex">
|
||||
<div :class="`catalog__icon catalog__icon--${getCSSModifier(dataset.category)}`"></div>
|
||||
<p class="text is-caption">{{ dataset.category }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-cell grid-cell--col3">
|
||||
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.country') }}</h4>
|
||||
<div class="u-flex">
|
||||
<div :class="`catalog__icon catalog__icon--${getCSSModifier(dataset.country)}`"></div>
|
||||
<p class="text is-caption">{{ dataset.country }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import toObject from 'new-dashboard/utils/to-object';
|
||||
import getCSSModifier from 'new-dashboard/utils/get-css-modifier';
|
||||
import sendCustomEvent from 'new-dashboard/utils/send-custom-event';
|
||||
import Page from 'new-dashboard/components/Page';
|
||||
import SecondaryNavigation from 'new-dashboard/components/SecondaryNavigation';
|
||||
import SectionTitle from 'new-dashboard/components/SectionTitle';
|
||||
import CatalogRequestSuccess from 'new-dashboard/components/Catalog/CatalogRequestSuccess';
|
||||
|
||||
export default {
|
||||
name: 'CatalogDetail',
|
||||
components: {
|
||||
Page,
|
||||
SecondaryNavigation,
|
||||
SectionTitle,
|
||||
CatalogRequestSuccess
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
hasBeenSuccesfullyRequested: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: state => state.user,
|
||||
datasets: state => state.catalog.list,
|
||||
isFetchingDatasets: state => state.catalog.isFetching,
|
||||
dataset (state) {
|
||||
if (this.isFetchingDatasets) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const datasetList = toObject(this.datasets, 'id');
|
||||
const selectedDataset = datasetList[this.$route.params.id];
|
||||
sendCustomEvent('catalogSelectDataset', {
|
||||
catalogSelectedDataset: selectedDataset.name
|
||||
});
|
||||
return selectedDataset;
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getCSSModifier,
|
||||
requestDataset () {
|
||||
this.$store.dispatch('catalog/requestDataset', { user: this.user, dataset: this.dataset })
|
||||
.then(
|
||||
() => {
|
||||
this.hasBeenSuccesfullyRequested = true;
|
||||
this.sendCustomCatalogEvents(this.dataset);
|
||||
}
|
||||
);
|
||||
},
|
||||
sendCustomCatalogEvents (dataset) {
|
||||
sendCustomEvent('catalogRequestDataset', {
|
||||
catalogRequestedDataset: dataset.name
|
||||
});
|
||||
sendCustomEvent('catalogRequestCountry', {
|
||||
catalogRequestedCountry: dataset.country
|
||||
});
|
||||
sendCustomEvent('catalogRequestCategory', {
|
||||
catalogRequestedCategory: dataset.category
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "new-dashboard/styles/variables";
|
||||
|
||||
.page--data {
|
||||
padding-top: 64px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.catalogDetail {
|
||||
margin-top: 64px;
|
||||
|
||||
&__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px 0;
|
||||
|
||||
&--icon {
|
||||
width: 7px;
|
||||
height: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
list-style-position: inside;
|
||||
list-style-type: disc;
|
||||
|
||||
&--item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.catalog__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 12px;
|
||||
background-size: contain;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<Page class="page--data">
|
||||
<SecondaryNavigation>
|
||||
<div class="tabs">
|
||||
<router-link :to="{ name: 'datasets' }" class="tabs__item title is-small" exact active-class="is-active" :class="{'is-active': isDatasetPage }">
|
||||
<span>{{ $t('DataPage.tabs.yourDatasets') }}</span>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'catalog' }" class="tabs__item title is-small" active-class="is-active">
|
||||
<span>{{ $t('DataPage.tabs.catalog') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</SecondaryNavigation>
|
||||
<router-view></router-view>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from 'new-dashboard/components/Page';
|
||||
import SecondaryNavigation from 'new-dashboard/components/SecondaryNavigation';
|
||||
import { isAllowed } from 'new-dashboard/core/configuration/filters';
|
||||
|
||||
export default {
|
||||
name: 'DataPage',
|
||||
components: {
|
||||
Page,
|
||||
SecondaryNavigation
|
||||
},
|
||||
computed: {
|
||||
isDatasetPage () {
|
||||
return isAllowed(this.$route.params.filter);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "new-dashboard/styles/variables";
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
|
||||
&__item {
|
||||
margin-right: 48px;
|
||||
padding: 24px 0 20px;
|
||||
border-bottom: 4px solid transparent;
|
||||
background: none;
|
||||
color: $color-primary;
|
||||
text-transform: none;
|
||||
|
||||
&.is-active {
|
||||
border-color: currentColor;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<section class="datasets-section">
|
||||
<StickySubheader :is-visible="Boolean(selectedDatasets.length && isScrollPastHeader)">
|
||||
<h2 class="title is-caption">
|
||||
{{ $t('BulkActions.selected', {count: selectedDatasets.length}) }}
|
||||
</h2>
|
||||
<DatasetBulkActions
|
||||
:selectedDatasets="selectedDatasets"
|
||||
:areAllDatasetsSelected="areAllDatasetsSelected"
|
||||
@selectAll="selectAll"
|
||||
@deselectAll="deselectAll"></DatasetBulkActions>
|
||||
</StickySubheader>
|
||||
|
||||
<DatasetsList
|
||||
ref="datasetsList"
|
||||
class="grid__content"
|
||||
:hasBulkActions="true"
|
||||
:canHoverCard="true"
|
||||
:maxVisibleDatasets="maxVisibleDatasets"
|
||||
@applyFilter="applyFilter"
|
||||
@applyOrder="applyOrder"
|
||||
@selectionChange="updateSelected" />
|
||||
<Pagination v-if="shouldShowPagination" :page=currentPage :numPages=numPages @pageChange="goToPage"></Pagination>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState } from 'vuex';
|
||||
import { checkFilters } from 'new-dashboard/router/hooks/check-navigation';
|
||||
import Pagination from 'new-dashboard/components/Pagination';
|
||||
import DatasetBulkActions from 'new-dashboard/components/BulkActions/DatasetBulkActions.vue';
|
||||
import StickySubheader from '../../components/StickySubheader';
|
||||
import DatasetsList from 'new-dashboard/components/DatasetsList.vue';
|
||||
|
||||
export default {
|
||||
name: 'DatasetsPage',
|
||||
components: {
|
||||
DatasetBulkActions,
|
||||
StickySubheader,
|
||||
Pagination,
|
||||
DatasetsList
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isScrollPastHeader: false,
|
||||
maxVisibleDatasets: 12,
|
||||
selectedDatasets: []
|
||||
};
|
||||
},
|
||||
beforeMount () {
|
||||
if (this.$store.getters['user/isViewer']) {
|
||||
// Redirect to shared datasets page if user is viewer
|
||||
return this.$router.replace({ name: 'datasets', params: { filter: 'shared' } });
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.stickyScrollPosition = this.getHeaderBottomPageOffset();
|
||||
this.$onScrollChange = this.onScrollChange.bind(this);
|
||||
document.addEventListener('scroll', this.$onScrollChange, { passive: true });
|
||||
},
|
||||
beforeDestroy () {
|
||||
document.removeEventListener('scroll', this.$onScrollChange, { passive: true });
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
checkFilters('datasets', 'datasets', to, from, next);
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
numPages: state => state.datasets.numPages,
|
||||
currentPage: state => state.datasets.page,
|
||||
datasets: state => state.datasets.list,
|
||||
isFetchingDatasets: state => state.datasets.isFetching,
|
||||
numResults: state => state.datasets.metadata.total_entries
|
||||
}),
|
||||
isNotificationVisible () {
|
||||
return this.$store.getters['user/isNotificationVisible'];
|
||||
},
|
||||
areAllDatasetsSelected () {
|
||||
return Object.keys(this.datasets).length === this.selectedDatasets.length;
|
||||
},
|
||||
shouldShowPagination () {
|
||||
return !this.isFetchingDatasets && this.numResults > 0 && this.numPages > 1;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateSelected (selected) {
|
||||
this.selectedDatasets = selected;
|
||||
},
|
||||
goToPage (page) {
|
||||
this.deselectAll();
|
||||
window.scroll({ top: 0, left: 0 });
|
||||
|
||||
this.$router.push({
|
||||
name: 'datasets',
|
||||
params: this.$route.params,
|
||||
query: {...this.$route.query, page}
|
||||
});
|
||||
},
|
||||
applyFilter (filter) {
|
||||
this.$router.push({ name: 'datasets', params: { filter } });
|
||||
},
|
||||
applyOrder (orderParams) {
|
||||
this.deselectAll();
|
||||
this.$router.push({
|
||||
name: 'datasets',
|
||||
params: this.$route.params,
|
||||
query: {
|
||||
...this.$route.query,
|
||||
page: 1,
|
||||
order: orderParams.order,
|
||||
order_direction: orderParams.direction
|
||||
}
|
||||
});
|
||||
},
|
||||
selectAll () {
|
||||
this.$refs.datasetsList.selectAll();
|
||||
},
|
||||
deselectAll () {
|
||||
this.$refs.datasetsList.deselectAll();
|
||||
},
|
||||
onScrollChange () {
|
||||
this.isScrollPastHeader = window.pageYOffset > this.stickyScrollPosition;
|
||||
},
|
||||
getHeaderBottomPageOffset () {
|
||||
const headerContainer = this.$refs.datasetsList.getHeaderContainer();
|
||||
const headerBoundingClientRect = headerContainer.$el.getBoundingClientRect();
|
||||
const notificationHeight = this.isNotificationVisible ? 60 : 0;
|
||||
return headerBoundingClientRect.top - notificationHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'new-dashboard/styles/variables';
|
||||
|
||||
.datasets-section {
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user