mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
update menu + add event page
This commit is contained in:
@@ -20,74 +20,57 @@ type Props = {
|
||||
sections: Section[]
|
||||
}
|
||||
|
||||
const { isOpen, open, close, toggle } = useMenu()
|
||||
|
||||
const route = useRoute();
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { isAdmin } = useUserRoles();
|
||||
|
||||
|
||||
let resizeHandler: any;
|
||||
|
||||
onMounted(() => {
|
||||
resizeHandler = () => {
|
||||
isMenuTooLarge.value = innerHeight < 720;
|
||||
}
|
||||
addEventListener('resize', resizeHandler);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeHandler) removeEventListener('resize', resizeHandler);
|
||||
})
|
||||
|
||||
const isMenuTooLarge = ref<boolean>(false);
|
||||
|
||||
const { isOpen, close } = useMenu();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-0 md:w-[5rem] absolute top-0 md:relative h-full">
|
||||
<div @mouseover="open()" @mouseleave="close()"
|
||||
class="CVerticalNavigation absolute z-[80] bg-menu h-full overflow-hidden w-0 md:w-[5rem]"
|
||||
:class="{ '!w-[18rem] shadow-[0_0_20px_#000000] rounded-r-2xl': isOpen }">
|
||||
<div :class="{ 'w-[18rem]': isOpen }">
|
||||
<div class="flex gap-4 items-center py-6 px-[.9rem] pb-8" :class="{ '!pb-4': isMenuTooLarge }">
|
||||
<div class="bg-black h-[2.8rem] aspect-[1/1] flex items-center justify-center rounded-lg">
|
||||
<img class="h-[2.4rem]" :src="'/logo.png'">
|
||||
</div>
|
||||
<div v-if="isOpen" class="font-bold text-[1.4rem] text-gray-300"> Litlyx </div>
|
||||
<div class="CVerticalNavigation h-full w-[20rem] bg-[#111111] flex shadow-[1px_0_10px_#000000] rounded-r-lg" :class="{
|
||||
'absolute top-0 w-full md:w-[20rem] z-[45] open': isOpen,
|
||||
'hidden lg:flex': !isOpen
|
||||
}">
|
||||
<div class="p-4 gap-6 flex flex-col w-full">
|
||||
|
||||
<div class="flex items-center gap-2 ml-2">
|
||||
<div class="bg-black h-[2.4rem] aspect-[1/1] flex items-center justify-center rounded-lg">
|
||||
<img class="h-[2rem]" :src="'/logo.png'">
|
||||
</div>
|
||||
<div class="font-bold text-[1.4rem] text-gray-300"> Litlyx </div>
|
||||
|
||||
<div class="grow flex justify-end text-[1.4rem] mr-2 lg:hidden">
|
||||
<i @click="close()" class="fas fa-close"></i>
|
||||
</div>
|
||||
|
||||
<div class="pb-8" :class="{ '!pb-3': isMenuTooLarge }" v-for="section of sections">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col px-3 gap-2" :class="{ '!gap-[.3rem]': isMenuTooLarge }">
|
||||
<div class="flex flex-col gap-4">
|
||||
|
||||
<template v-for="entry of section.entries">
|
||||
<div v-for="section of sections" class="flex flex-col gap-1">
|
||||
|
||||
<NuxtLink @click="entry.action?.()" :target="entry.external ? '_blank' : ''"
|
||||
v-if="entry.to && (!entry.adminOnly || (isAdmin && !isAdminHidden))" tag="div"
|
||||
:to="entry.to || '/'"
|
||||
class="text-[#a3a9b6] flex w-full items-center gap-3 p-3 rounded-lg cursor-pointer hover:bg-[#363638] hover:text-[#ffffff]"
|
||||
:class="{
|
||||
'brightness-[.4] pointer-events-none': entry.disabled,
|
||||
'bg-[#363638] shadow-[0px_0px_2px_#ffffff20_inset] border-[#ffffff20] border-[1px] !text-[#ffffff]': route.path == (entry.to || '#')
|
||||
}">
|
||||
<div class="flex items-center text-[1.4rem] w-[1.8rem] justify-center">
|
||||
<div v-for="entry of section.entries">
|
||||
|
||||
<div class="bg-[#111111] text-gray-300 hover:bg-[#1b1b1b] py-2 px-4 rounded-lg" :class="{
|
||||
'text-gray-700 pointer-events-none': entry.disabled,
|
||||
'bg-[#1b1b1b]': route.path == (entry.to || '#')
|
||||
}">
|
||||
|
||||
<NuxtLink @click="close() && entry.action?.()" :target="entry.external ? '_blank' : ''"
|
||||
v-if="(!entry.adminOnly || (isAdmin && !isAdminHidden))" tag="div" class="flex"
|
||||
:to="entry.to || '/'">
|
||||
<div class="flex items-center w-[1.8rem] justify-start">
|
||||
<i :class="entry.icon"></i>
|
||||
</div>
|
||||
<div v-if="isOpen" class="text-[.9rem] font-bold manrope"> {{ entry.label }} </div>
|
||||
<div class="manrope">
|
||||
{{ entry.label }}
|
||||
</div>
|
||||
</NuxtLink>
|
||||
|
||||
<div v-if="!entry.to" @click="entry.action?.()"
|
||||
class="text-[#a3a9b6] flex w-full items-center gap-3 p-3 rounded-lg cursor-pointer hover:bg-[#363638] hover:text-[#ffffff]">
|
||||
<div class="flex items-center text-[1.4rem] w-[1.8rem] justify-center">
|
||||
<i :class="entry.icon"></i>
|
||||
</div>
|
||||
<div v-if="isOpen" class="text-[.9rem] font-bold manrope"> {{ entry.label }} </div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -100,12 +83,9 @@ const isMenuTooLarge = ref<boolean>(false);
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.CVerticalNavigation {
|
||||
transition: all .25s ease-in-out;
|
||||
}
|
||||
|
||||
.CVerticalNavigation * {
|
||||
font-family: 'Inter';
|
||||
font-family: 'Geist';
|
||||
}
|
||||
|
||||
input:focus {
|
||||
|
||||
@@ -15,7 +15,8 @@ type Props = {
|
||||
interactive?: boolean,
|
||||
isDetailView?: boolean,
|
||||
rawButton?: boolean,
|
||||
hideShowMore?: boolean
|
||||
hideShowMore?: boolean,
|
||||
customIconStyle?: string
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<{
|
||||
@@ -97,8 +98,12 @@ function showDetails(id: string) {
|
||||
<div class="flex px-2 py-1 relative items-center gap-4">
|
||||
<div v-if="iconProvider && iconProvider(element._id) != undefined"
|
||||
class="flex items-center h-[1.3rem]">
|
||||
<img v-if="iconProvider(element._id)?.[0] == 'img'" class="h-full"
|
||||
:src="iconProvider(element._id)?.[1]">
|
||||
|
||||
<img v-if="iconProvider(element._id)?.[0] == 'img'"
|
||||
class="h-full"
|
||||
:style="customIconStyle"
|
||||
:src="iconProvider(element._id)?.[1]">
|
||||
|
||||
<i v-else :class="iconProvider(element._id)?.[1]"></i>
|
||||
</div>
|
||||
<span class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { CountriesAggregated } from '~/server/api/metrics/[project_id]/data/countries';
|
||||
import type { IconProvider } from './BarsCard.vue';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: countries, pending, refresh } = await useFetch<CountriesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/countries`, signHeaders());
|
||||
|
||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
||||
return [
|
||||
'img',
|
||||
`https://raw.githubusercontent.com/hampusborgos/country-flags/main/png250px/${id.toLowerCase()}.png`
|
||||
]
|
||||
}
|
||||
|
||||
const customIconStyle = `width: 2rem; padding: 1px;`
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
@@ -29,7 +39,9 @@ function showMore() {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="countries || []" :dataIcons="false" :loading="pending"
|
||||
label="Top Countries" sub-label="Countries" desc=" Lists the countries where users access your website."></DashboardBarsCard>
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="countries || []" :dataIcons="false"
|
||||
:loading="pending" label="Top Countries" sub-label="Countries" :iconProvider="iconProvider"
|
||||
:customIconStyle="customIconStyle" desc=" Lists the countries where users access your website.">
|
||||
</DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -86,7 +86,7 @@ onMounted(async () => {
|
||||
|
||||
|
||||
<template>
|
||||
<div class="gap-6 px-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-4" v-if="metricsInfo">
|
||||
<div class="gap-6 px-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 m-cards-wrap:grid-cols-4" v-if="metricsInfo">
|
||||
|
||||
<DashboardCountCard :ready="visitsData.ready" icon="far fa-earth" text="Total page visits"
|
||||
:value="formatNumberK(metricsInfo.visitsCount)" :avg="formatNumberK(avgVisitDay) + '/day'"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
|
||||
const eventNames = ref<string[]>([]);
|
||||
const selectedEventName = ref<string>();
|
||||
const metadataFields = ref<string[]>([]);
|
||||
|
||||
57
dashboard/components/events/EventsUserFlow.vue
Normal file
57
dashboard/components/events/EventsUserFlow.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const eventNames = ref<string[]>([]);
|
||||
const selectedEventName = ref<string>();
|
||||
|
||||
onMounted(async () => {
|
||||
eventNames.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/names`, signHeaders());
|
||||
});
|
||||
|
||||
const userFlowData = ref<any>();
|
||||
const analyzing = ref<boolean>(false);
|
||||
|
||||
async function analyzeEvent() {
|
||||
userFlowData.value = undefined;
|
||||
analyzing.value = true;
|
||||
userFlowData.value = await $fetch(`/api/metrics/${activeProject.value?._id.toString()}/events/flow_from_name?name=${selectedEventName.value}`, signHeaders());
|
||||
analyzing.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardTitled title="Event User Flow"
|
||||
sub="Track your user's journey from external links to custom events within your platform." class="w-full p-4">
|
||||
|
||||
<div class="p-2 flex flex-col gap-3">
|
||||
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
||||
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
|
||||
</USelectMenu>
|
||||
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
|
||||
<div @click="analyzeEvent()"
|
||||
class="bg-bg w-fit px-8 py-2 poppins rounded-lg hover:bg-bg/80 cursor-pointer">
|
||||
Analyze
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="analyzing">
|
||||
Analyzing...
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2" v-if="userFlowData">
|
||||
<div class="flex gap-4 items-center bg-bg py-1 px-2 rounded-lg" v-for="(count, referrer) in userFlowData">
|
||||
<div class="w-5 h-5 flex items-center justify-center">
|
||||
<img :src="`https://s2.googleusercontent.com/s2/favicons?domain=${referrer}&sz=64`" :alt="'referrer'">
|
||||
</div>
|
||||
<div> {{ referrer }} </div>
|
||||
<div class="grow"></div>
|
||||
<div> {{ count }} </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</CardTitled>
|
||||
</template>
|
||||
Reference in New Issue
Block a user