mirror of
https://github.com/directus/directus.git
synced 2026-02-01 08:15:19 -05:00
Add dashboard creation flow
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -90,5 +90,9 @@ body {
|
||||
max-width: calc(100% - 16px);
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
::v-deep .v-button {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -545,6 +545,8 @@ dashboard: Dashboard
|
||||
panel: Panel
|
||||
no_dashboards: No Dashboards
|
||||
no_dashboards_copy: You don’t have any Dashboards yet.
|
||||
create_dashboard: Create Dashboard
|
||||
dashboard_name: Dashboard Name
|
||||
image_url: Image Url
|
||||
alt_text: Alternative Text
|
||||
media: Media
|
||||
|
||||
@@ -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>
|
||||
35
app/src/modules/insights/components/navigation.vue
Normal file
35
app/src/modules/insights/components/navigation.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user