Files
webapp/app/layouts/map.vue
2026-01-07 09:10:35 +07:00

122 lines
3.4 KiB
Vue

<template>
<div class="drawer lg:drawer-open">
<input id="main-drawer" type="checkbox" class="drawer-toggle" v-model="drawerOpen" />
<!-- Main content area -->
<div class="drawer-content flex flex-col h-screen bg-base-200">
<!-- TopBar -->
<TopBar
:session-checked="sessionChecked"
:logged-in="isLoggedIn"
:novu-subscriber-id="novuSubscriberId"
:user-avatar-svg="userAvatarSvg"
:user-name="userName"
:user-initials="userInitials"
@toggle-sidebar="drawerOpen = !drawerOpen"
@sign-out="onClickSignOut"
@sign-in="signIn()"
/>
<!-- Page content - full height for map -->
<main class="flex-1 flex flex-col min-h-0">
<slot />
</main>
</div>
<!-- Sidebar drawer -->
<div class="drawer-side z-50">
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<slot name="sidebar" />
</div>
</div>
</template>
<script setup lang="ts">
const runtimeConfig = useRuntimeConfig()
const siteUrl = runtimeConfig.public.siteUrl || 'https://optovia.ru/'
const { signIn, signOut, loggedIn, fetch: fetchSession } = useAuth()
const drawerOpen = ref(false)
const userData = useState<{
id?: string
firstName?: string
lastName?: string
avatarId?: string
activeTeam?: { name?: string; teamType?: string; logtoOrgId?: string }
activeTeamId?: string
teams?: Array<{ id?: string; name?: string; logtoOrgId?: string }>
} | null>('me', () => null)
const sessionChecked = ref(false)
const userAvatarSvg = useState('user-avatar-svg', () => '')
const novuSubscriberId = useState('novu-subscriber-id', () => '')
const isLoggedIn = computed(() => loggedIn.value || !!userData.value?.id)
const userName = computed(() => {
return userData.value?.firstName || 'User'
})
const userInitials = computed(() => {
const first = userData.value?.firstName?.charAt(0) || ''
const last = userData.value?.lastName?.charAt(0) || ''
if (first || last) return (first + last).toUpperCase()
return '?'
})
// Avatar generation
const generateUserAvatar = async () => {
const seed = userData.value?.avatarId || userData.value?.id || userData.value?.firstName || 'default'
try {
const response = await fetch(`https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}&backgroundColor=b6e3f4,c0aede,d1d4f9`)
if (response.ok) {
userAvatarSvg.value = await response.text()
}
} catch (error) {
console.error('Error generating avatar:', error)
}
}
const computeNovuSubscriber = () => {
const uuid = userData.value?.id
if (uuid) return uuid
return ''
}
const { setActiveTeam } = useActiveTeam()
const syncUserUi = async () => {
if (!userData.value) {
novuSubscriberId.value = ''
return
}
if (userData.value.activeTeamId && userData.value.activeTeam?.logtoOrgId) {
setActiveTeam(userData.value.activeTeamId, userData.value.activeTeam.logtoOrgId)
}
novuSubscriberId.value = computeNovuSubscriber()
if (!userAvatarSvg.value) {
await generateUserAvatar()
}
}
watch(userData, () => {
void syncUserUi()
}, { immediate: true })
// Check session (SSR + client navigation)
await fetchSession().catch(() => {})
sessionChecked.value = true
const onClickSignOut = () => {
signOut(siteUrl)
}
// Close drawer on route change (mobile)
const route = useRoute()
watch(() => route.path, () => {
drawerOpen.value = false
})
</script>