232 lines
7.4 KiB
Vue
232 lines
7.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarGroupLabel,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuSubItem,
|
|
SidebarRail,
|
|
} from '@/components/ui/sidebar'
|
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
|
import { LayoutGrid, Boxes, Settings, Home, ChevronRight, Database, Layers, LogOut } from 'lucide-vue-next'
|
|
|
|
const { logout } = useAuth()
|
|
const { api } = useApi()
|
|
|
|
const handleLogout = async () => {
|
|
await logout()
|
|
}
|
|
|
|
// Fetch objects and group by app
|
|
const apps = ref<any[]>([])
|
|
const topLevelObjects = ref<any[]>([])
|
|
const loading = ref(true)
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const response = await api.get('/setup/objects')
|
|
const allObjects = response.data || response || []
|
|
|
|
// Group objects by app
|
|
const appMap = new Map<string, any>()
|
|
const noAppObjects: any[] = []
|
|
|
|
allObjects.forEach((obj: any) => {
|
|
if (obj.appId) {
|
|
if (!appMap.has(obj.appId)) {
|
|
appMap.set(obj.appId, {
|
|
id: obj.appId,
|
|
name: obj.app?.name || obj.app?.label || 'Unknown App',
|
|
label: obj.app?.label || obj.app?.name || 'Unknown App',
|
|
objects: []
|
|
})
|
|
}
|
|
appMap.get(obj.appId)!.objects.push(obj)
|
|
} else {
|
|
noAppObjects.push(obj)
|
|
}
|
|
})
|
|
|
|
apps.value = Array.from(appMap.values())
|
|
topLevelObjects.value = noAppObjects
|
|
} catch (e) {
|
|
console.error('Failed to load objects:', e)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
const staticMenuItems = [
|
|
{
|
|
title: 'Home',
|
|
url: '/',
|
|
icon: Home,
|
|
},
|
|
{
|
|
title: 'Setup',
|
|
icon: Settings,
|
|
items: [
|
|
{
|
|
title: 'Apps',
|
|
url: '/setup/apps',
|
|
icon: LayoutGrid,
|
|
},
|
|
{
|
|
title: 'Objects',
|
|
url: '/setup/objects',
|
|
icon: Boxes,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton size="lg" as-child>
|
|
<NuxtLink to="/">
|
|
<div
|
|
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground"
|
|
>
|
|
<LayoutGrid class="size-4" />
|
|
</div>
|
|
<div class="flex flex-col gap-0.5 leading-none">
|
|
<span class="font-semibold">Neo Platform</span>
|
|
<span class="text-xs">Multi-tenant CRM</span>
|
|
</div>
|
|
</NuxtLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<!-- Static Menu Items -->
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
<template v-for="item in staticMenuItems" :key="item.title">
|
|
<!-- Simple menu item -->
|
|
<SidebarMenuItem v-if="!item.items">
|
|
<SidebarMenuButton as-child>
|
|
<NuxtLink :to="item.url">
|
|
<component :is="item.icon" />
|
|
<span>{{ item.title }}</span>
|
|
</NuxtLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
|
|
<!-- Collapsible menu item with submenu -->
|
|
<Collapsible v-else as-child :default-open="false" class="group/collapsible">
|
|
<SidebarMenuItem>
|
|
<CollapsibleTrigger as-child>
|
|
<SidebarMenuButton :tooltip="item.title">
|
|
<component :is="item.icon" />
|
|
<span>{{ item.title }}</span>
|
|
<ChevronRight
|
|
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
|
/>
|
|
</SidebarMenuButton>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub>
|
|
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
|
|
<SidebarMenuSubButton as-child>
|
|
<NuxtLink :to="subItem.url">
|
|
<component :is="subItem.icon" />
|
|
<span>{{ subItem.title }}</span>
|
|
</NuxtLink>
|
|
</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
</SidebarMenuItem>
|
|
</Collapsible>
|
|
</template>
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
|
|
<!-- Top-level Objects (no app) -->
|
|
<SidebarGroup v-if="!loading && topLevelObjects.length > 0">
|
|
<SidebarGroupLabel>Objects</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem v-for="obj in topLevelObjects" :key="obj.id">
|
|
<SidebarMenuButton as-child>
|
|
<NuxtLink :to="`/${obj.apiName.toLowerCase()}`">
|
|
<Database class="h-4 w-4" />
|
|
<span>{{ obj.label || obj.apiName }}</span>
|
|
</NuxtLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
|
|
<!-- App-grouped Objects -->
|
|
<SidebarGroup v-if="!loading && apps.length > 0">
|
|
<SidebarGroupLabel>Apps</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
<Collapsible
|
|
v-for="app in apps"
|
|
:key="app.id"
|
|
as-child
|
|
:default-open="true"
|
|
class="group/collapsible"
|
|
>
|
|
<SidebarMenuItem>
|
|
<CollapsibleTrigger as-child>
|
|
<SidebarMenuButton :tooltip="app.label">
|
|
<LayoutGrid class="h-4 w-4" />
|
|
<span>{{ app.label }}</span>
|
|
<ChevronRight
|
|
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
|
/>
|
|
</SidebarMenuButton>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub>
|
|
<SidebarMenuSubItem v-for="obj in app.objects" :key="obj.id">
|
|
<SidebarMenuSubButton as-child>
|
|
<NuxtLink :to="`/${obj.apiName.toLowerCase()}`">
|
|
<Database class="h-4 w-4" />
|
|
<span>{{ obj.label || obj.apiName }}</span>
|
|
</NuxtLink>
|
|
</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
</SidebarMenuItem>
|
|
</Collapsible>
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
<SidebarFooter>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton @click="handleLogout" class="cursor-pointer hover:bg-accent">
|
|
<LogOut class="h-4 w-4" />
|
|
<span>Logout</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarFooter>
|
|
<SidebarRail />
|
|
</Sidebar>
|
|
</template>
|