Files
ai-service-front/src/components/AppSidebar.vue
2026-02-15 12:26:14 +03:00

197 lines
8.1 KiB
Vue

<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useProjectsStore } from '@/stores/projectsStore'
import { storeToRefs } from 'pinia'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const projectsStore = useProjectsStore()
const { projects, currentProject } = storeToRefs(projectsStore)
const selectedProject = ref(null)
onMounted(async () => {
// Ensure we have projects
if (projects.value.length === 0) {
await projectsStore.fetchProjects()
}
// Sync local ref with store
if (currentProject.value) {
selectedProject.value = currentProject.value.id
}
})
// Watch for external changes (like selecting from the list view)
watch(currentProject, (newVal) => {
selectedProject.value = newVal ? newVal.id : null
})
const projectOptions = computed(() => {
return projects.value.map(p => ({ name: p.name, id: p.id }))
})
const getProjectName = (id) => {
const p = projects.value.find(p => p.id === id)
return p ? p.name : 'Unknown Project'
}
const isProjectMenuOpen = ref(false)
const selectProject = (projectId) => {
selectedProject.value = projectId
isProjectMenuOpen.value = false
if (projectId) {
projectsStore.selectProject(projectId)
} else {
// Clear selection
projectsStore.currentProject = null
localStorage.removeItem('active_project_id')
}
// Reload page to ensure all data is refreshed with new context
window.location.reload()
}
const handleLogout = () => {
authStore.logout()
router.push('/login')
}
// Check active route for styling
const isActive = (path) => {
// Exact match for home, startsWith for others
if (path === '/') return route.path === '/'
return route.path.startsWith(path)
}
const navItems = computed(() => {
const items = [
{ path: '/', icon: '🏠', tooltip: 'Home' },
{ path: '/projects', icon: '📂', tooltip: 'Projects' },
{ path: '/ideas', icon: '💡', tooltip: 'Ideas' },
{ path: '/flexible', icon: '🖌️', tooltip: 'Flexible' },
{ path: '/albums', icon: '🖼️', tooltip: 'Library' },
{ path: '/characters', icon: '👥', tooltip: 'Characters' }
]
if (authStore.isAdmin()) {
items.push({ path: '/admin/approvals', icon: '🛡️', tooltip: 'Approvals' })
}
return items
})
</script>
<template>
<div class="contents">
<!-- Sidebar (Desktop -> Top Bar) -->
<nav
class="hidden md:flex glass-panel w-[calc(100%-2rem)] mx-4 mt-4 mb-2 flex-row items-center px-8 py-3 rounded-2xl z-40 border border-white/5 bg-slate-900/50 backdrop-blur-md shrink-0 justify-between">
<!-- Logo -->
<img src="/web-app-manifest-512x512.png" alt="Logo"
class="w-10 h-10 rounded-xl shadow-lg shadow-violet-500/20 shrink-0" />
<!-- Project Switcher -->
<div class="hidden lg:block ml-4 relative">
<button @click="isProjectMenuOpen = !isProjectMenuOpen"
class="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-white/5 transition-colors text-slate-400 hover:text-slate-200">
<i v-if="selectedProject" class="pi pi-folder text-violet-400"></i>
<i v-else class="pi pi-user"></i>
<span class="max-w-[150px] truncate font-medium">
{{ selectedProject ? getProjectName(selectedProject) : 'Personal Workspace' }}
</span>
<i class="pi pi-chevron-down text-xs ml-1 opacity-50"></i>
</button>
<!-- Custom Dropdown Menu -->
<div v-if="isProjectMenuOpen"
class="absolute top-full left-0 mt-2 w-56 bg-slate-900 border border-white/10 shadow-xl rounded-xl overflow-hidden z-50 py-1">
<!-- Personal Workspace Option -->
<div @click="selectProject(null)"
class="flex items-center gap-3 px-4 py-3 hover:bg-white/5 cursor-pointer transition-colors"
:class="{ 'text-violet-400 bg-white/5': !selectedProject, 'text-slate-300': selectedProject }">
<i class="pi pi-user"></i>
<span class="font-medium">Personal Workspace</span>
<i v-if="!selectedProject" class="pi pi-check ml-auto text-sm"></i>
</div>
<div class="h-px bg-white/5 my-1"></div>
<!-- Project Options -->
<div v-for="project in projects" :key="project.id" @click="selectProject(project.id)"
class="flex items-center gap-3 px-4 py-3 hover:bg-white/5 cursor-pointer transition-colors"
:class="{ 'text-violet-400 bg-white/5': selectedProject === project.id, 'text-slate-300': selectedProject !== project.id }">
<i class="pi pi-folder"></i>
<span class="truncate">{{ project.name }}</span>
<i v-if="selectedProject === project.id" class="pi pi-check ml-auto text-sm"></i>
</div>
<div v-if="projects.length === 0" class="px-4 py-3 text-slate-500 text-sm font-italic">
No projects found
</div>
</div>
<!-- Backdrop to close on click outside -->
<div v-if="isProjectMenuOpen" @click="isProjectMenuOpen = false"
class="fixed inset-0 z-40 bg-transparent"></div>
</div>
<!-- Nav Items -->
<div class="flex flex-row gap-2 items-center justify-center flex-1 mx-8">
<div v-for="item in navItems" :key="item.path" :class="[
'px-4 py-2 flex items-center gap-2 rounded-xl cursor-pointer transition-all duration-300',
isActive(item.path)
? 'bg-white/10 text-slate-50 shadow-inner'
: 'text-slate-400 hover:bg-white/5 hover:text-slate-50'
]" @click="router.push(item.path)" v-tooltip.bottom="item.tooltip">
<span class="text-xl">{{ item.icon }}</span>
<span class="text-sm font-medium hidden lg:block">{{ item.tooltip }}</span>
</div>
</div>
<!-- Right Actions -->
<div class="flex items-center gap-4 shrink-0">
<div @click="handleLogout"
class="w-10 h-10 rounded-xl bg-red-500/10 text-red-400 flex items-center justify-center cursor-pointer hover:bg-red-500/20 transition-all font-bold"
v-tooltip.bottom="'Logout'">
<i class="pi pi-power-off"></i>
</div>
<!-- Profile Avatar Placeholder -->
<div class="w-10 h-10 rounded-full bg-slate-800 border-2 border-violet-600 flex items-center justify-center font-bold text-slate-50 cursor-pointer hover:scale-105 transition-all"
title="Profile">
U
</div>
</div>
</nav>
<!-- Mobile Bottom Nav -->
<nav
class="md:hidden fixed bottom-0 left-0 right-0 h-16 bg-slate-900/90 backdrop-blur-xl border-t border-white/10 z-50 flex justify-around items-center px-2">
<div v-for="item in navItems" :key="item.path" :class="[
'flex flex-col items-center gap-1 p-2 rounded-xl transition-all',
isActive(item.path)
? 'text-white bg-white/10 relative top-[-10px] shadow-lg shadow-violet-500/20 border border-violet-500/30'
: 'text-slate-400 hover:text-slate-200'
]" @click="router.push(item.path)">
<span class="text-xl">{{ item.icon }}</span>
</div>
</nav>
</div>
</template>
<style scoped>
.glass-panel {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
</style>