feat: stabilize admin dashboard, refactor group management, implement PWA, and fix routing
This commit is contained in:
@@ -23,7 +23,19 @@
|
||||
<div class="flex-1 flex flex-col relative min-w-0">
|
||||
<!-- Toolbar -->
|
||||
<div class="h-10 bg-slate-900 border-b border-slate-700 flex items-center px-3 gap-2 z-20">
|
||||
<input v-model="canvasName" @change="saveCanvas" class="text-sm font-semibold text-slate-300 mr-2 bg-transparent border border-transparent hover:border-slate-600 focus:border-blue-500 focus:bg-slate-800 outline-none rounded px-2 py-1 w-64 transition" title="Переименовать карту" />
|
||||
<input v-model="canvasName" @change="saveCanvas" class="text-sm font-semibold text-slate-300 mr-2 bg-transparent border border-transparent hover:border-slate-600 focus:border-blue-500 focus:bg-slate-800 outline-none rounded px-2 py-1 w-48 transition" title="Переименовать карту" />
|
||||
|
||||
<!-- Group Selection -->
|
||||
<div class="flex items-center gap-1 bg-slate-800 border border-slate-700 rounded px-2 py-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase font-bold mr-1">Группа:</span>
|
||||
<select v-model="selectedGroupId" @change="saveCanvas" class="bg-transparent text-xs text-slate-300 outline-none cursor-pointer hover:text-white transition">
|
||||
<option :value="null">Личная (нет)</option>
|
||||
<option v-for="g in groupsList" :key="g.id" :value="g.id">
|
||||
👥 {{ g.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span v-if="isDirty" class="px-2 py-1 bg-yellow-600 text-yellow-100 rounded text-xs font-medium">
|
||||
● Несохранено
|
||||
</span>
|
||||
@@ -204,6 +216,8 @@ const authHeaders = computed(() => ({ Authorization: `Bearer ${auth.token.value}
|
||||
const elements = ref([])
|
||||
const selectedNode = ref(null)
|
||||
const canvasName = ref(props.canvasId)
|
||||
const selectedGroupId = ref(null)
|
||||
const groupsList = ref([])
|
||||
|
||||
const nodeTypes = [
|
||||
{ type: 'text', name: 'Текст', icon: '📝', description: 'Markdown заметка' },
|
||||
@@ -219,6 +233,7 @@ const loadData = async () => {
|
||||
const data = await loadCanvas(props.canvasId)
|
||||
elements.value = transformToFlowElements(data)
|
||||
canvasName.value = data.name || props.canvasId
|
||||
selectedGroupId.value = data.groupId || null
|
||||
// Mark initial state as saved
|
||||
markAsSaved(data)
|
||||
}
|
||||
@@ -639,7 +654,7 @@ const saveCanvas = async () => {
|
||||
console.log('[DEBUG saveCanvas] Sending PUT request...')
|
||||
const res = await $fetch(`/api/canvas/${props.canvasId}`, {
|
||||
method: 'PUT',
|
||||
body: { ...obsidianCanvas, name: canvasName.value },
|
||||
body: { ...obsidianCanvas, name: canvasName.value, groupId: selectedGroupId.value },
|
||||
headers: authHeaders.value
|
||||
})
|
||||
console.log('[DEBUG saveCanvas] PUT request successful!', res)
|
||||
@@ -690,6 +705,14 @@ onMounted(async () => {
|
||||
} catch (e) {
|
||||
console.error('Failed to load canvases', e)
|
||||
}
|
||||
|
||||
// Load groups for assignment
|
||||
try {
|
||||
const data = await $fetch('/api/groups')
|
||||
groupsList.value = data.groups || []
|
||||
} catch (e) {
|
||||
console.error('Failed to load groups', e)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div v-if="deferredPrompt && !isStandalone" class="fixed bottom-6 right-6 z-50">
|
||||
<button
|
||||
@click="installApp"
|
||||
class="bg-blue-600 hover:bg-blue-500 text-white px-6 py-3 rounded-2xl font-bold shadow-2xl flex items-center gap-3 transition-all transform hover:scale-105 active:scale-95 group"
|
||||
>
|
||||
<span class="text-xl group-hover:rotate-12 transition-transform">📲</span>
|
||||
<span>Установить XFlow</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const deferredPrompt = ref(null)
|
||||
const isStandalone = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
isStandalone.value = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault()
|
||||
deferredPrompt.value = e
|
||||
})
|
||||
|
||||
window.addEventListener('appinstalled', () => {
|
||||
deferredPrompt.value = null
|
||||
})
|
||||
})
|
||||
|
||||
const installApp = async () => {
|
||||
if (!deferredPrompt.value) {
|
||||
alert('Браузер еще не подготовил установку. Пожалуйста, подождите 10-20 секунд или проверьте вкладку Application в консоли.')
|
||||
return
|
||||
}
|
||||
|
||||
deferredPrompt.value.prompt()
|
||||
const { outcome } = await deferredPrompt.value.userChoice
|
||||
console.log(`PWA: user choice outcome: ${outcome}`)
|
||||
deferredPrompt.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes bounce-subtle {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
}
|
||||
.animate-bounce-subtle {
|
||||
animation: bounce-subtle 3s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user