Move insights to Vue 3

This commit is contained in:
rijkvanzanten
2021-06-11 20:30:15 -04:00
parent 69d47476d0
commit 4492b07c2e
16 changed files with 144 additions and 104 deletions

View File

@@ -4,13 +4,14 @@
:disabled="disabled"
:items="items"
@update:model-value="$emit('input', $event)"
:placeholder="$t('select_a_collection')"
:placeholder="t('select_a_collection')"
/>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useCollectionsStore } from '@/stores/';
import { useI18n } from 'vue-i18n';
export default defineComponent({
emits: ['input'],
@@ -29,6 +30,8 @@ export default defineComponent({
},
},
setup(props) {
const { t } = useI18n();
const collectionsStore = useCollectionsStore();
const collections = computed(() => {
@@ -46,7 +49,7 @@ export default defineComponent({
}));
});
return { items };
return { items, t };
},
});
</script>

View File

@@ -12,7 +12,7 @@
:model-value="value"
:disabled="disabled"
:items="selectItems"
:placeholder="$t('select_a_field')"
:placeholder="t('select_a_field')"
/>
</template>

View File

@@ -1,27 +1,27 @@
<template>
<v-dialog :active="active" @toggle="$listeners.toggle" persistent @esc="cancel">
<v-dialog :active="active" @toggle="$emit('toggle', $event)" persistent @esc="cancel">
<template #activator="slotBinding">
<slot name="activator" v-bind="slotBinding" />
</template>
<v-card>
<v-card-title v-if="!dashboard">{{ $t('create_dashboard') }}</v-card-title>
<v-card-title v-else>{{ $t('edit_dashboard') }}</v-card-title>
<v-card-title v-if="!dashboard">{{ t('create_dashboard') }}</v-card-title>
<v-card-title v-else>{{ t('edit_dashboard') }}</v-card-title>
<v-card-text>
<div class="fields">
<v-input autofocus v-model="values.name" :placeholder="$t('dashboard_name')" />
<v-input autofocus v-model="values.name" :placeholder="t('dashboard_name')" />
<interface-select-icon v-model="values.icon" />
<v-input class="full" v-model="values.note" :placeholder="$t('note')" />
<v-input class="full" v-model="values.note" :placeholder="t('note')" />
</div>
</v-card-text>
<v-card-actions>
<v-button @click="cancel" secondary>
{{ $t('cancel') }}
{{ t('cancel') }}
</v-button>
<v-button :disabled="!values.name" @click="save" :loading="saving">
{{ $t('save') }}
{{ t('save') }}
</v-button>
</v-card-actions>
</v-card>
@@ -31,10 +31,11 @@
<script lang="ts">
import api from '@/api';
import { unexpectedError } from '@/utils/unexpected-error';
import { defineComponent, ref, reactive, PropType } from '@vue/composition-api';
import { defineComponent, ref, reactive, PropType } from 'vue';
import { useInsightsStore } from '@/stores';
import router from '@/router';
import { router } from '@/router';
import { Dashboard } from '@/types';
import { useI18n } from 'vue-i18n';
export default defineComponent({
name: 'DashboardDialog',
@@ -53,6 +54,8 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const { t } = useI18n();
const insightsStore = useInsightsStore();
const values = reactive({
@@ -63,7 +66,7 @@ export default defineComponent({
const saving = ref(false);
return { values, cancel, saving, save };
return { values, cancel, saving, save, t };
function cancel() {
emit('toggle', false);

View File

@@ -10,7 +10,7 @@
</template>
<script lang="ts">
import { defineComponent, computed, ref } from '@vue/composition-api';
import { defineComponent, computed, ref } from 'vue';
import { useInsightsStore } from '@/stores';
import { Dashboard } from '@/types';
@@ -22,7 +22,7 @@ export default defineComponent({
const createDialogActive = ref(false);
const navItems = computed(() =>
insightsStore.state.dashboards.map((dashboard: Dashboard) => ({
insightsStore.dashboards.map((dashboard: Dashboard) => ({
icon: dashboard.icon,
name: dashboard.name,
to: `/insights/${dashboard.id}`,

View File

@@ -21,18 +21,23 @@
</div>
<div class="edit-actions" v-if="editMode">
<v-icon class="duplicate-icon" name="control_point_duplicate" v-tooltip="$t('duplicate')" @click.stop="$emit('duplicate')" />
<v-icon
class="duplicate-icon"
name="control_point_duplicate"
v-tooltip="t('duplicate')"
@click.stop="$emit('duplicate')"
/>
<v-icon
class="edit-icon"
name="edit"
v-tooltip="$t('edit')"
v-tooltip="t('edit')"
@click.stop="$router.push(`/insights/${panel.dashboard}/${panel.id}`)"
/>
<v-icon class="delete-icon" name="clear" v-tooltip="$t('delete')" @click.stop="$emit('delete')" />
<v-icon class="delete-icon" name="clear" v-tooltip="t('delete')" @click.stop="$emit('delete')" />
</div>
<div class="resize-details">
({{positioning.x - 1}}:{{positioning.y - 1}}) {{positioning.width}}×{{positioning.height}}
({{ positioning.x - 1 }}:{{ positioning.y - 1 }}) {{ positioning.width }}×{{ positioning.height }}
</div>
<div class="resize-handlers" v-if="editMode">
@@ -55,8 +60,9 @@
<script lang="ts">
import { getPanels } from '@/panels';
import { Panel } from '@/types';
import { defineComponent, PropType, computed, ref, reactive } from '@vue/composition-api';
import { defineComponent, PropType, computed, ref, reactive } from 'vue';
import { throttle } from 'lodash';
import { useI18n } from 'vue-i18n';
export default defineComponent({
name: 'panel',
@@ -71,6 +77,7 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const { t } = useI18n();
const { panels } = getPanels();
const panelTypeInfo = computed(() => {
@@ -122,6 +129,7 @@ export default defineComponent({
onPointerMove,
dragging,
editedPosition,
t,
};
function useDragDrop() {
@@ -260,16 +268,35 @@ export default defineComponent({
}
.panel.editing.dragging {
z-index: 3 !important;
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
z-index: 3 !important;
}
.resize-details {
position: absolute;
top: 0;
right: 0;
z-index: 2;
padding: 17px 14px;
color: var(--foreground-subdued);
font-weight: 500;
font-size: 15px;
font-family: var(--family-monospace);
font-style: normal;
line-height: 1;
text-align: right;
background-color: var(--background-page);
border-top-right-radius: var(--border-radius-outline);
opacity: 0;
transition: opacity var(--fast) var(--transition), color var(--fast) var(--transition);
pointer-events: none;
}
.panel.editing.dragging .resize-details {
opacity: 1;
}
.panel-content {
position: relative;
width: 100%;
@@ -333,27 +360,6 @@ export default defineComponent({
border-top-right-radius: var(--border-radius-outline);
}
.resize-details {
transition: opacity var(--fast) var(--transition),
color var(--fast) var(--transition);
position: absolute;
z-index: 2;
top: 0;
right: 0;
padding: 17px 14px;
color: var(--foreground-subdued);
font-weight: 500;
font-size: 15px;
font-family: var(--family-monospace);
font-style: normal;
line-height: 1;
text-align: right;
background-color: var(--background-page);
border-top-right-radius: var(--border-radius-outline);
pointer-events: none;
opacity: 0;
}
.resize-handlers div {
position: absolute;
z-index: 2;

View File

@@ -10,12 +10,12 @@ export default defineModule({
routes: [
{
name: 'insights-overview',
path: '/',
path: '',
component: InsightsOverview,
},
{
name: 'insights-dashboard',
path: '/:primaryKey',
path: ':primaryKey',
component: InsightsDashboard,
props: true,
children: [

View File

@@ -8,14 +8,14 @@
</template>
<template #headline>
<v-breadcrumb :items="[{ name: $t('insights'), to: '/insights' }]" />
<v-breadcrumb :items="[{ name: t('insights'), to: '/insights' }]" />
</template>
<template #actions>
<template v-if="editMode">
<v-button
class="clear-changes"
v-tooltip.bottom="$t('clear_changes')"
v-tooltip.bottom="t('clear_changes')"
rounded
icon
outlined
@@ -24,11 +24,11 @@
<v-icon name="clear" />
</v-button>
<v-button rounded icon outlined v-tooltip.bottom="$t('add_new')" :to="`/insights/${currentDashboard.id}/+`">
<v-button rounded icon outlined v-tooltip.bottom="t('add_new')" :to="`/insights/${currentDashboard.id}/+`">
<v-icon name="add" />
</v-button>
<v-button rounded icon v-tooltip.bottom="$t('save')" @click="saveChanges" :loading="saving">
<v-button rounded icon v-tooltip.bottom="t('save')" @click="saveChanges" :loading="saving">
<v-icon name="check" />
</v-button>
</template>
@@ -69,14 +69,14 @@
<v-dialog :active="!!confirmDeletePanel" @esc="confirmDeletePanel = null">
<v-card>
<v-card-title>{{ $t('panel_delete_confirm') }}</v-card-title>
<v-card-title>{{ t('panel_delete_confirm') }}</v-card-title>
<v-card-actions>
<v-button @click="confirmDeletePanel = null" secondary>
{{ $t('cancel') }}
{{ t('cancel') }}
</v-button>
<v-button class="action-delete" @click="deletePanel" :loading="deletingPanel">
{{ $t('delete') }}
{{ t('delete') }}
</v-button>
</v-card-actions>
</v-card>
@@ -86,16 +86,17 @@
<script lang="ts">
import InsightsNavigation from '../components/navigation.vue';
import { defineComponent, computed, ref } from '@vue/composition-api';
import { defineComponent, computed, ref } from 'vue';
import { useInsightsStore } from '@/stores';
import InsightsNotFound from './not-found.vue';
import { Panel } from '@/types';
import { nanoid } from 'nanoid';
import { cloneDeep, merge, omit } from 'lodash';
import router from '@/router';
import { merge, omit } from 'lodash';
import { router } from '@/router';
import { unexpectedError } from '@/utils/unexpected-error';
import api from '@/api';
import InsightsPanel from '../components/panel.vue';
import { useI18n } from 'vue-i18n';
import { pointOnLine } from '@/utils/point-on-line';
export default defineComponent({
@@ -112,6 +113,8 @@ export default defineComponent({
},
},
setup(props) {
const { t } = useI18n();
const editMode = ref(false);
const confirmDeletePanel = ref<string | null>(null);
const deletingPanel = ref(false);
@@ -119,7 +122,7 @@ export default defineComponent({
const insightsStore = useInsightsStore();
const currentDashboard = computed(() =>
insightsStore.state.dashboards.find((dashboard) => dashboard.id === props.primaryKey)
insightsStore.dashboards.find((dashboard) => dashboard.id === props.primaryKey)
);
const stagedPanels = ref<Partial<Panel & { borderRadius: [boolean, boolean, boolean, boolean] }>[]>([]);
@@ -238,6 +241,7 @@ export default defineComponent({
confirmDeletePanel,
cancelChanges,
duplicatePanel,
t,
};
function stagePanelEdits(edits: Partial<Panel>, key: string = props.panelKey) {

View File

@@ -1,23 +1,29 @@
<template>
<private-view :title="$t('insights')">
<private-view :title="t('insights')">
<template #navigation>
<insights-navigation />
</template>
<div class="not-found" v-if="!currentDashboard">
<v-info :title="$t('page_not_found')" icon="not_interested">
{{ $t('page_not_found_body') }}
<v-info :title="t('page_not_found')" icon="not_interested">
{{ t('page_not_found_body') }}
</v-info>
</div>
</private-view>
</template>
<script>
import { defineComponent } from '@vue/composition-api';
import { defineComponent } from 'vue';
import InsightsNavigation from '../components/navigation.vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
components: { InsightsNavigation },
setup() {
const { t } = useI18n();
return { t };
},
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<private-view :title="$t('insights')">
<private-view :title="t('insights')">
<template #title-outer:prepend>
<v-button rounded disabled icon secondary>
<v-icon name="dashboard" />
@@ -17,7 +17,7 @@
@click="on"
rounded
icon
v-tooltip.bottom="createAllowed ? $t('create_item') : $t('not_allowed')"
v-tooltip.bottom="createAllowed ? t('create_item') : t('not_allowed')"
:disabled="createAllowed === false"
>
<v-icon name="add" />
@@ -28,13 +28,13 @@
<v-table
v-if="dashboards.length > 0"
:headers.sync="tableHeaders"
v-model:headers="tableHeaders"
:items="dashboards"
show-resize
fixed-header
@click:row="navigateToDashboard"
>
<template #item.icon="{ item }">
<template #[`item.icon`]="{ item }">
<v-icon class="icon" :name="item.icon" />
</template>
@@ -50,7 +50,7 @@
<v-icon name="edit" outline />
</v-list-item-icon>
<v-list-item-content>
{{ $t('edit_dashboard') }}
{{ t('edit_dashboard') }}
</v-list-item-content>
</v-list-item>
@@ -59,7 +59,7 @@
<v-icon name="delete" outline />
</v-list-item-icon>
<v-list-item-content>
{{ $t('delete_dashboard') }}
{{ t('delete_dashboard') }}
</v-list-item-content>
</v-list-item>
</v-list>
@@ -67,20 +67,20 @@
</template>
</v-table>
<v-info icon="dashboard" :title="$t('no_dashboards')" v-else center>
{{ $t('no_dashboards_copy') }}
<v-info icon="dashboard" :title="t('no_dashboards')" v-else center>
{{ t('no_dashboards_copy') }}
</v-info>
<v-dialog :active="!!confirmDelete" @esc="confirmDelete = null">
<v-card>
<v-card-title>{{ $t('dashboard_delete_confirm') }}</v-card-title>
<v-card-title>{{ t('dashboard_delete_confirm') }}</v-card-title>
<v-card-actions>
<v-button @click="confirmDelete = null" secondary>
{{ $t('cancel') }}
{{ t('cancel') }}
</v-button>
<v-button class="action-delete" @click="deleteDashboard" :loading="deletingDashboard">
{{ $t('delete') }}
{{ t('delete') }}
</v-button>
</v-card-actions>
</v-card>
@@ -91,11 +91,11 @@
</template>
<script lang="ts">
import { defineComponent, computed, ref } from '@vue/composition-api';
import { defineComponent, computed, ref } from 'vue';
import { useInsightsStore, usePermissionsStore } from '@/stores';
import i18n from '@/lang';
import { useI18n } from 'vue-i18n';
import { Dashboard } from '@/types';
import router from '@/router';
import { router } from '@/router';
import InsightsNavigation from '../components/navigation.vue';
import DashboardDialog from '../components/dashboard-dialog.vue';
import api from '@/api';
@@ -105,6 +105,8 @@ export default defineComponent({
name: 'InsightsOverview',
components: { InsightsNavigation, DashboardDialog },
setup() {
const { t } = useI18n();
const insightsStore = useInsightsStore();
const permissionsStore = usePermissionsStore();
@@ -126,18 +128,18 @@ export default defineComponent({
sortable: false,
},
{
text: i18n.t('name'),
text: t('name'),
value: 'name',
width: 240,
},
{
text: i18n.t('note'),
text: t('note'),
value: 'note',
width: 360,
},
];
const dashboards = computed(() => insightsStore.state.dashboards);
const dashboards = computed(() => insightsStore.dashboards);
return {
dashboards,
@@ -149,6 +151,7 @@ export default defineComponent({
deletingDashboard,
deleteDashboard,
editDashboard,
t,
};
function navigateToDashboard(dashboard: Dashboard) {

View File

@@ -1,19 +1,19 @@
<template>
<v-drawer active :title="(panel && panel.name) || $t('panel')" @cancel="$emit('cancel')" icon="insert_chart">
<v-drawer active :title="(panel && panel.name) || t('panel')" @cancel="$emit('cancel')" icon="insert_chart">
<template #actions>
<v-button :disabled="!edits.type" @click="emitSave" icon rounded v-tooltip.bottom="$t('done')">
<v-button :disabled="!edits.type" @click="emitSave" icon rounded v-tooltip.bottom="t('done')">
<v-icon name="check" />
</v-button>
</template>
<div class="content">
<p class="type-label panel-type-label">{{ $t('type') }}</p>
<p class="type-label panel-type-label">{{ t('type') }}</p>
<v-fancy-select class="select" :items="selectItems" v-model="edits.type" />
<template v-if="edits.type && selectedPanel">
<v-notice v-if="!selectedPanel.options || selectedPanel.options.length === 0">
{{ $t('no_options_available') }}
{{ t('no_options_available') }}
</v-notice>
<v-form
@@ -29,33 +29,42 @@
<v-divider :inline-title="false" large>
<template #icon><v-icon name="info" /></template>
<template #default>{{ $t('panel_header') }}</template>
<template #default>{{ t('panel_header') }}</template>
</v-divider>
<div class="form-grid">
<div class="field half-left">
<p class="type-label">{{ $t('visible') }}</p>
<v-checkbox block v-model="edits.show_header" :label="$t('show_header')" />
<p class="type-label">{{ t('visible') }}</p>
<v-checkbox block v-model="edits.show_header" :label="t('show_header')" />
</div>
<div class="field half-right">
<p class="type-label">{{ $t('name') }}</p>
<v-input :nullable="false" v-model="edits.name" :disabled="edits.show_header !== true" :placeholder="$t('panel_name_placeholder')" />
<p class="type-label">{{ t('name') }}</p>
<v-input
:nullable="false"
v-model="edits.name"
:disabled="edits.show_header !== true"
:placeholder="t('panel_name_placeholder')"
/>
</div>
<div class="field half-left">
<p class="type-label">{{ $t('icon') }}</p>
<p class="type-label">{{ t('icon') }}</p>
<interface-select-icon v-model="edits.icon" :disabled="edits.show_header !== true" />
</div>
<div class="field half-right">
<p class="type-label">{{ $t('color') }}</p>
<p class="type-label">{{ t('color') }}</p>
<interface-select-color v-model="edits.color" :disabled="edits.show_header !== true" width="half" />
</div>
<div class="field full">
<p class="type-label">{{ $t('note') }}</p>
<v-input v-model="edits.note" :disabled="edits.show_header !== true" :placeholder="$t('panel_note_placeholder')" />
<p class="type-label">{{ t('note') }}</p>
<v-input
v-model="edits.note"
:disabled="edits.show_header !== true"
:placeholder="t('panel_note_placeholder')"
/>
</div>
</div>
</div>
@@ -63,10 +72,11 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, watch, PropType } from '@vue/composition-api';
import { computed, defineComponent, reactive, watch, PropType } from 'vue';
import { getPanels } from '@/panels';
import { FancySelectItem } from '@/components/v-fancy-select/types';
import { Panel } from '@/types';
import { useI18n } from 'vue-i18n';
export default defineComponent({
name: 'PanelConfiguration',
@@ -77,6 +87,8 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const { t } = useI18n();
const { panels } = getPanels();
const edits = reactive<Partial<Panel>>({
@@ -126,6 +138,7 @@ export default defineComponent({
close,
emitSave,
edits,
t,
};
function emitSave() {

View File

@@ -1,4 +1,4 @@
import { ref, Ref } from '@vue/composition-api';
import { ref, Ref } from 'vue';
import { PanelConfig } from './types';
let panelsRaw: Ref<PanelConfig[]>;

View File

@@ -5,7 +5,7 @@
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent } from 'vue';
export default defineComponent({
props: {

View File

@@ -10,7 +10,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, watch } from '@vue/composition-api';
import { defineComponent, ref, watch } from 'vue';
import api from '@/api';
import { isEqual } from 'lodash';
@@ -62,7 +62,9 @@ export default defineComponent({
},
});
metric.value = Number(res.data.data[0].result).toLocaleString(undefined, {minimumFractionDigits: props.options.decimals ?? 2});
metric.value = Number(res.data.data[0].result).toLocaleString(undefined, {
minimumFractionDigits: props.options.decimals ?? 2,
});
} catch (err) {
// oh no
} finally {

View File

@@ -3,7 +3,7 @@
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch, onMounted, onUnmounted } from '@vue/composition-api';
import { defineComponent, PropType, ref, watch, onMounted, onUnmounted } from 'vue';
import api from '@/api';
import ApexCharts from 'apexcharts';
@@ -38,11 +38,11 @@ export default defineComponent({
fetchData();
console.log(props.options.color + "test");
console.log(props.options.color + 'test');
onMounted(() => {
chart.value = new ApexCharts(chartEl.value, {
colors: [(props.options.color ? props.options.color : 'var(--primary)')],
colors: [props.options.color ? props.options.color : 'var(--primary)'],
chart: {
type: 'area',
height: '100%',
@@ -71,12 +71,12 @@ export default defineComponent({
[
{
offset: 0,
color: (props.options.color ? props.options.color : 'var(--primary)'),
color: props.options.color ? props.options.color : 'var(--primary)',
opacity: 0.25,
},
{
offset: 100,
color: (props.options.color ? props.options.color : 'var(--primary)'),
color: props.options.color ? props.options.color : 'var(--primary)',
opacity: 0,
},
],

View File

@@ -1,8 +1,8 @@
import { Dashboard } from '../types';
import api from '@/api';
import { createStore } from 'pinia';
import { defineStore } from 'pinia';
export const useInsightsStore = createStore({
export const useInsightsStore = defineStore({
id: 'insightsStore',
state: () => ({
dashboards: [] as Dashboard[],
@@ -13,7 +13,7 @@ export const useInsightsStore = createStore({
params: { limit: -1, fields: ['*', 'panels.*'], sort: ['name'] },
});
this.state.dashboards = response.data.data;
this.dashboards = response.data.data;
},
dehydrate() {
this.reset();

View File

@@ -49,7 +49,7 @@ export const usePermissionsStore = defineStore({
},
hasPermission(collection: string, action: Permission['action']) {
const userStore = useUserStore();
if (userStore.state.currentUser?.role?.admin_access === true) return true;
if (userStore.currentUser?.role?.admin_access === true) return true;
return !!this.getPermissionsForUser(collection, action);
},
},