mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-30 20:23:46 +02:00
feat: add dashboard
This commit is contained in:
186
src/views/Dashboard/Dashboard.vue
Normal file
186
src/views/Dashboard/Dashboard.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="x-container flex h-full min-h-0 flex-col gap-3 py-3">
|
||||
<DashboardEditToolbar
|
||||
v-if="isEditing"
|
||||
v-model:name="editName"
|
||||
@save="handleSave"
|
||||
@cancel="handleCancelEdit"
|
||||
@delete="handleDelete" />
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
|
||||
<template v-if="displayRows.length && !isEditing">
|
||||
<ResizablePanelGroup direction="vertical" :auto-save-id="`dashboard-${id}`" class="flex-1 min-h-0">
|
||||
<template v-for="(row, rowIndex) in displayRows" :key="rowIndex">
|
||||
<ResizablePanel :default-size="100 / displayRows.length" :min-size="10">
|
||||
<DashboardRow :row="row" :row-index="rowIndex" :dashboard-id="id" />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle v-if="rowIndex < displayRows.length - 1" />
|
||||
</template>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isEditing">
|
||||
<DashboardRow
|
||||
v-for="(row, rowIndex) in displayRows"
|
||||
:key="rowIndex"
|
||||
:row="row"
|
||||
:row-index="rowIndex"
|
||||
:dashboard-id="id"
|
||||
:is-editing="true"
|
||||
@update-panel="handleUpdatePanel"
|
||||
@remove-row="handleRemoveRow" />
|
||||
|
||||
<div
|
||||
class="mt-auto flex min-h-[80px] flex-1 items-center justify-center rounded-md border-2 border-dashed border-muted-foreground/20 text-muted-foreground transition-colors hover:border-primary/40 hover:bg-primary/5"
|
||||
:class="showAddRowOptions ? 'items-start p-4' : 'cursor-pointer'"
|
||||
@click="handleAddRowAreaClick">
|
||||
<div v-if="showAddRowOptions" class="flex flex-wrap items-center gap-3">
|
||||
<span class="text-xs text-muted-foreground">{{ t('dashboard.actions.add_row') }}:</span>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-10 w-16 items-center justify-center rounded-md border-2 border-dashed border-muted-foreground/30 transition-colors hover:border-primary/50 hover:bg-primary/5"
|
||||
@click.stop="handleAddRow(1)">
|
||||
<div class="h-6 w-12 rounded bg-muted-foreground/20" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-10 w-16 items-center justify-center gap-1 rounded-md border-2 border-dashed border-muted-foreground/30 transition-colors hover:border-primary/50 hover:bg-primary/5"
|
||||
@click.stop="handleAddRow(2)">
|
||||
<div class="h-6 w-5 rounded bg-muted-foreground/20" />
|
||||
<div class="h-6 w-5 rounded bg-muted-foreground/20" />
|
||||
</button>
|
||||
</div>
|
||||
<Plus v-else class="size-6 opacity-50" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-1 items-center justify-center rounded-md border border-dashed text-muted-foreground">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<p>{{ t('dashboard.empty') }}</p>
|
||||
<Button @click="isEditing = true">{{ t('dashboard.actions.start_editing') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Plus } from 'lucide-vue-next';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
|
||||
import { useDashboardStore, useModalStore } from '@/stores';
|
||||
|
||||
import DashboardEditToolbar from './components/DashboardEditToolbar.vue';
|
||||
import DashboardRow from './components/DashboardRow.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const modalStore = useModalStore();
|
||||
|
||||
const isEditing = ref(false);
|
||||
const showAddRowOptions = ref(false);
|
||||
const editRows = ref([]);
|
||||
const editName = ref('');
|
||||
|
||||
const dashboard = computed(() => dashboardStore.getDashboard(props.id));
|
||||
const displayRows = computed(() => (isEditing.value ? editRows.value : dashboard.value?.rows || []));
|
||||
|
||||
const cloneRows = (rows) => JSON.parse(JSON.stringify(Array.isArray(rows) ? rows : []));
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
isEditing.value = false;
|
||||
showAddRowOptions.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
dashboard,
|
||||
(value) => {
|
||||
if (!value) {
|
||||
router.replace({ name: 'feed' });
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(isEditing, (editing) => {
|
||||
if (!editing || !dashboard.value) {
|
||||
showAddRowOptions.value = false;
|
||||
return;
|
||||
}
|
||||
editRows.value = cloneRows(dashboard.value.rows);
|
||||
editName.value = dashboard.value.name || '';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => dashboardStore.editingDashboardId,
|
||||
(editingId) => {
|
||||
if (editingId === props.id) {
|
||||
isEditing.value = true;
|
||||
dashboardStore.clearEditingDashboardId();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const handleAddRowAreaClick = () => {
|
||||
showAddRowOptions.value = !showAddRowOptions.value;
|
||||
};
|
||||
|
||||
const handleAddRow = (panelCount) => {
|
||||
const panels = Array(panelCount).fill(null);
|
||||
editRows.value.push({ panels });
|
||||
showAddRowOptions.value = false;
|
||||
};
|
||||
|
||||
const handleRemoveRow = (rowIndex) => {
|
||||
editRows.value.splice(rowIndex, 1);
|
||||
};
|
||||
|
||||
const handleUpdatePanel = (rowIndex, panelIndex, panelKey) => {
|
||||
if (!editRows.value[rowIndex]?.panels) {
|
||||
return;
|
||||
}
|
||||
editRows.value[rowIndex].panels[panelIndex] = panelKey;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
await dashboardStore.updateDashboard(props.id, {
|
||||
name: editName.value.trim() || dashboard.value?.name || 'Dashboard',
|
||||
rows: editRows.value
|
||||
});
|
||||
isEditing.value = false;
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
isEditing.value = false;
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
const { ok } = await modalStore.confirm({
|
||||
title: t('dashboard.confirmations.delete_title'),
|
||||
description: t('dashboard.confirmations.delete_description')
|
||||
});
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
await dashboardStore.deleteDashboard(props.id);
|
||||
router.replace({ name: 'feed' });
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user