Add dashboard creation flow

This commit is contained in:
rijkvanzanten
2021-05-25 18:18:17 -04:00
parent 56841ca66b
commit c79b48e08f
7 changed files with 185 additions and 10 deletions

View File

@@ -3,8 +3,8 @@ import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('directus_dashboards', (table) => {
table.uuid('id').primary();
table.string('name');
table.string('icon', 30);
table.string('name').notNullable();
table.string('icon', 30).notNullable().defaultTo('dashboard');
table.text('note');
table.timestamp('date_created').defaultTo(knex.fn.now());
table.uuid('user_created').references('id').inTable('directus_users').onDelete('SET NULL');
@@ -14,14 +14,15 @@ export async function up(knex: Knex): Promise<void> {
table.uuid('id').primary();
table.uuid('dashboard').notNullable().references('id').inTable('directus_dashboards').onDelete('CASCADE');
table.string('name');
table.string('icon', 30);
table.string('icon', 30).defaultTo('insert_chart');
table.string('color', 10);
table.boolean('show_header').notNullable().defaultTo(true);
table.text('note');
table.string('type');
table.integer('position_x');
table.integer('position_y');
table.integer('width');
table.integer('height');
table.string('type').notNullable();
table.integer('position_x').notNullable();
table.integer('position_y').notNullable();
table.integer('width').notNullable();
table.integer('height').notNullable();
table.json('options');
table.timestamp('date_created').defaultTo(knex.fn.now());
table.uuid('user_created').references('id').inTable('directus_users').onDelete('SET NULL');

View File

@@ -90,5 +90,9 @@ body {
max-width: calc(100% - 16px);
margin: 8px;
}
::v-deep .v-button {
pointer-events: all;
}
}
</style>

View File

@@ -545,6 +545,8 @@ dashboard: Dashboard
panel: Panel
no_dashboards: No Dashboards
no_dashboards_copy: You dont have any Dashboards yet.
create_dashboard: Create Dashboard
dashboard_name: Dashboard Name
image_url: Image Url
alt_text: Alternative Text
media: Media

View File

@@ -0,0 +1,89 @@
<template>
<v-dialog :active="active" @toggle="$listeners.toggle" persistent @esc="cancel">
<template #activator="slotBinding">
<slot name="activator" v-bind="slotBinding" />
</template>
<v-card>
<v-card-title>{{ $t('create_dashboard') }}</v-card-title>
<v-card-text>
<div class="fields">
<v-input @keyup.enter="save" autofocus v-model="dashboardName" :placeholder="$t('dashboard_name')" />
<interface-select-icon v-model="dashboardIcon" />
</div>
</v-card-text>
<v-card-actions>
<v-button @click="cancel" secondary>
{{ $t('cancel') }}
</v-button>
<v-button :disabled="dashboardName === null || dashboardName.length === 0" @click="save" :loading="saving">
{{ $t('save') }}
</v-button>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import api from '@/api';
import { unexpectedError } from '@/utils/unexpected-error';
import { defineComponent, ref } from '@vue/composition-api';
import { useInsightsStore } from '@/stores';
import router from '@/router';
export default defineComponent({
model: {
prop: 'active',
event: 'toggle',
},
props: {
active: {
type: Boolean,
default: false,
},
},
setup(props, { emit }) {
const insightsStore = useInsightsStore();
const dashboardName = ref(null);
const dashboardIcon = ref(null);
const saving = ref(false);
return { dashboardName, cancel, saving, save, dashboardIcon };
function cancel() {
dashboardName.value = null;
emit('toggle', false);
}
async function save() {
saving.value = true;
try {
const response = await api.post(
'/dashboards',
{ name: dashboardName.value, icon: dashboardIcon.value },
{ params: { fields: ['id'] } }
);
await insightsStore.hydrate();
router.push(`/insights/${response.data.data.id}`);
emit('toggle', false);
} catch (err) {
unexpectedError(err);
} finally {
saving.value = false;
}
}
},
});
</script>
<style scoped>
.fields {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<v-list large>
<v-list-item v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="navItem.name" />
</v-list-item-content>
</v-list-item>
</v-list>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from '@vue/composition-api';
import { useInsightsStore } from '@/stores';
import { Dashboard } from '@/types';
export default defineComponent({
name: 'InsightsNavigation',
setup() {
const insightsStore = useInsightsStore();
const createDialogActive = ref(false);
const navItems = computed(() =>
insightsStore.state.dashboards.map((dashboard: Dashboard) => ({
icon: dashboard.icon,
name: dashboard.name,
to: `/insights/${dashboard.id}`,
}))
);
return { navItems, createDialogActive };
},
});
</script>

View File

@@ -1,5 +1,18 @@
<template>
<private-view title="Insights">
<template #navigation>
<insights-navigation />
</template>
<div>Dashboard</div>
</private-view>
</template>
<script>
import InsightsNavigation from '../components/navigation.vue';
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'InsightsDashboard',
components: { InsightsNavigation },
});
</script>

View File

@@ -6,6 +6,26 @@
</v-button>
</template>
<template #navigation>
<insights-navigation />
</template>
<template #actions>
<create-dashboard-dialog v-model="createDialogActive">
<template #activator="{ on }">
<v-button
@click="on"
rounded
icon
v-tooltip.bottom="createAllowed ? $t('create_item') : $t('not_allowed')"
:disabled="createAllowed === false"
>
<v-icon name="add" />
</v-button>
</template>
</create-dashboard-dialog>
</template>
<v-table
v-if="dashboards.length > 0"
:headers.sync="tableHeaders"
@@ -26,18 +46,23 @@
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
import { defineComponent, computed, ref } from '@vue/composition-api';
import { useInsightsStore, usePermissionsStore } from '@/stores';
import i18n from '@/lang';
import { Dashboard } from '@/types';
import router from '@/router';
import InsightsNavigation from '../components/navigation.vue';
import CreateDashboardDialog from '../components/create-dashboard-dialog.vue';
export default defineComponent({
name: 'InsightsOverview',
components: { InsightsNavigation, CreateDashboardDialog },
setup() {
const insightsStore = useInsightsStore();
const permissionsStore = usePermissionsStore();
const createDialogActive = ref(false);
const createAllowed = computed<boolean>(() => {
return permissionsStore.hasPermission('directus_dashboards', 'create');
});
@@ -61,7 +86,13 @@ export default defineComponent({
},
];
return { dashboards: insightsStore.state.dashboards, createAllowed, tableHeaders, navigateToDashboard };
return {
dashboards: insightsStore.state.dashboards,
createAllowed,
tableHeaders,
navigateToDashboard,
createDialogActive,
};
function navigateToDashboard(dashboard: Dashboard) {
router.push(`/insights/${dashboard.id}`);