mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-13 08:48:38 +01:00
Rewrite endpoints + bar cards
This commit is contained in:
@@ -3,12 +3,8 @@ import { onMounted } from 'vue';
|
||||
import DateService, { type Slice } from '@services/DateService';
|
||||
import type { ChartData, ChartOptions, TooltipModel } from 'chart.js';
|
||||
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||
registerChartComponents();
|
||||
|
||||
const errorData = ref<{ errored: boolean, text: string }>({
|
||||
errored: false,
|
||||
text: ''
|
||||
})
|
||||
const errorData = ref<{ errored: boolean, text: string }>({ errored: false, text: '' })
|
||||
|
||||
const chartOptions = ref<ChartOptions<'line'>>({
|
||||
responsive: true,
|
||||
@@ -102,7 +98,6 @@ const chartData = ref<ChartData<'line' | 'bar' | 'bubble'>>({
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const { lineChartProps, lineChartRef, update: updateChart } = useLineChart({ chartData: (chartData as any), options: chartOptions });
|
||||
|
||||
const externalTooltipElement = ref<null | HTMLDivElement>(null);
|
||||
@@ -135,8 +130,6 @@ function externalTooltipHandler(context: { chart: any, tooltip: TooltipModel<'li
|
||||
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
|
||||
|
||||
}
|
||||
|
||||
|
||||
const selectLabels: { label: string, value: Slice }[] = [
|
||||
{ label: 'Hour', value: 'hour' },
|
||||
{ label: 'Day', value: 'day' },
|
||||
@@ -144,14 +137,9 @@ const selectLabels: { label: string, value: Slice }[] = [
|
||||
];
|
||||
|
||||
const selectedLabelIndex = ref<number>(1);
|
||||
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const allDatesFull = ref<string[]>([]);
|
||||
|
||||
|
||||
function transformResponse(input: { _id: string, count: number }[]) {
|
||||
const data = input.map(e => e.count);
|
||||
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, navigator.language, selectLabels[selectedLabelIndex.value].value));
|
||||
@@ -159,50 +147,31 @@ function transformResponse(input: { _id: string, count: number }[]) {
|
||||
return { data, labels }
|
||||
}
|
||||
|
||||
const body = computed(() => {
|
||||
return {
|
||||
from: safeSnapshotDates.value.from,
|
||||
to: safeSnapshotDates.value.to,
|
||||
slice: selectLabels[selectedLabelIndex.value].value
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function onResponseError(e: any) {
|
||||
console.log('ON RESPONSE ERROR')
|
||||
errorData.value = { errored: true, text: e.response._data.message ?? 'Generic error' }
|
||||
}
|
||||
|
||||
function onResponse(e: any) {
|
||||
console.log('ON RESPONSE')
|
||||
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
|
||||
}
|
||||
|
||||
const visitsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/visits`, {
|
||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||
lazy: true, immediate: false,
|
||||
onResponseError,
|
||||
onResponse
|
||||
|
||||
const visitsData = useFetch('/api/timeline/visits', {
|
||||
headers: useComputedHeaders({ slice: selectLabels[selectedLabelIndex.value].value }), lazy: true,
|
||||
transform: transformResponse, onResponseError, onResponse
|
||||
});
|
||||
|
||||
const eventsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events`, {
|
||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||
lazy: true, immediate: false,
|
||||
onResponseError,
|
||||
onResponse
|
||||
const sessionsData = useFetch('/api/timeline/sessions', {
|
||||
headers: useComputedHeaders({ slice: selectLabels[selectedLabelIndex.value].value }), lazy: true,
|
||||
transform: transformResponse, onResponseError, onResponse
|
||||
});
|
||||
|
||||
const sessionsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/sessions`, {
|
||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||
lazy: true, immediate: false,
|
||||
onResponseError,
|
||||
onResponse
|
||||
const eventsData = useFetch('/api/timeline/events', {
|
||||
headers: useComputedHeaders({ slice: selectLabels[selectedLabelIndex.value].value }), lazy: true,
|
||||
transform: transformResponse, onResponseError, onResponse
|
||||
});
|
||||
|
||||
|
||||
const readyToDisplay = computed(() => {
|
||||
return !visitsData.pending.value && !eventsData.pending.value && !sessionsData.pending.value;
|
||||
});
|
||||
const readyToDisplay = computed(() => !visitsData.pending.value && !eventsData.pending.value && !sessionsData.pending.value);
|
||||
|
||||
watch(readyToDisplay, () => {
|
||||
if (readyToDisplay.value === true) onDataReady();
|
||||
@@ -226,14 +195,10 @@ function createGradient(startColor: string) {
|
||||
}
|
||||
|
||||
function onDataReady() {
|
||||
console.log('DATA READY');
|
||||
|
||||
if (!visitsData.data.value) return;
|
||||
if (!eventsData.data.value) return;
|
||||
if (!sessionsData.data.value) return;
|
||||
|
||||
console.log('DATA READY 2');
|
||||
|
||||
chartData.value.labels = visitsData.data.value.labels;
|
||||
|
||||
const maxChartY = Math.max(...visitsData.data.value.data, ...sessionsData.data.value.data);
|
||||
@@ -250,9 +215,7 @@ function onDataReady() {
|
||||
chartData.value.datasets[1].backgroundColor = [createGradient('#4abde8')];
|
||||
chartData.value.datasets[2].backgroundColor = [createGradient('#fbbf24')];
|
||||
|
||||
console.log('UPDATE CHART');
|
||||
updateChart();
|
||||
|
||||
}
|
||||
|
||||
const currentTooltipData = ref<{ visits: number, events: number, sessions: number, date: string }>({
|
||||
@@ -268,19 +231,7 @@ function onLegendChange(dataset: any, index: number, checked: any) {
|
||||
dataset.hidden = !checked;
|
||||
}
|
||||
|
||||
const legendColors = [
|
||||
'#5655d7',
|
||||
'#4abde8',
|
||||
'#fbbf24'
|
||||
]
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
visitsData.execute();
|
||||
eventsData.execute();
|
||||
sessionsData.execute();
|
||||
});
|
||||
|
||||
const legendColors = ['#5655d7', '#4abde8', '#fbbf24']
|
||||
|
||||
const inLiveDemo = isLiveDemo();
|
||||
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
|
||||
export type IconProvider = (id: string) => ['img' | 'icon', string] | undefined;
|
||||
|
||||
|
||||
type Props = {
|
||||
data: { _id: string, count: number }[],
|
||||
iconProvider?: IconProvider,
|
||||
elementTextTransformer?: (text: string) => string,
|
||||
label: string,
|
||||
subLabel: string,
|
||||
desc: string,
|
||||
loading?: boolean,
|
||||
interactive?: boolean,
|
||||
isDetailView?: boolean,
|
||||
rawButton?: boolean,
|
||||
hideShowMore?: boolean,
|
||||
customIconStyle?: string,
|
||||
showLink?: boolean
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<{
|
||||
(e: 'dataReload'): void,
|
||||
(e: 'showDetails', id: string): void,
|
||||
(e: 'showRawData'): void,
|
||||
(e: 'showGeneral'): void,
|
||||
(e: 'showMore'): void,
|
||||
}>();
|
||||
|
||||
const maxData = computed(() => {
|
||||
const counts = props.data.map(e => e.count);
|
||||
return Math.max(...counts);
|
||||
});
|
||||
|
||||
function reloadData() {
|
||||
emits('dataReload');
|
||||
}
|
||||
|
||||
function showDetails(id: string) {
|
||||
emits('showDetails', id);
|
||||
}
|
||||
|
||||
function openExternalLink(link: string) {
|
||||
if (link === 'self') return;
|
||||
return window.open('https://' + link, '_blank');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<LyxUiCard class="w-full h-full p-4 flex flex-col gap-8 relative">
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="poppins font-semibold text-[1.4rem] text-text">
|
||||
{{ label }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i @click="reloadData()"
|
||||
class="hover:rotate-[50deg] transition-all duration-100 fas fa-refresh text-[1.2rem] cursor-pointer"></i>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="poppins text-[1rem] text-text-sub/90">
|
||||
{{ desc }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="rawButton" class="hidden lg:flex">
|
||||
<div @click="$emit('showRawData')"
|
||||
class="cursor-pointer flex gap-1 items-center justify-center font-semibold poppins rounded-lg text-[#5680f8] hover:text-[#5681f8ce]">
|
||||
<div> Raw data </div>
|
||||
<div class="flex items-center"> <i class="fas fa-arrow-up-right"></i> </div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="isDetailView" class="flex items-center justify-center">
|
||||
<i @click="$emit('showGeneral')"
|
||||
class="fas fa-arrow-left text-[.9rem] hover:text-text cursor-pointer"></i>
|
||||
</div>
|
||||
<div> {{ subLabel }} </div>
|
||||
</div>
|
||||
<div> Count </div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
|
||||
<div v-if="props.data.length > 0" class="flex justify-between items-center"
|
||||
v-for="element of props.data">
|
||||
|
||||
<div class="flex items-center gap-2 w-10/12 relative">
|
||||
|
||||
<div v-if="showLink">
|
||||
<i @click="openExternalLink(element._id)"
|
||||
class="fas fa-link text-gray-300 hover:text-gray-400 cursor-pointer"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1 items-center" @click="showDetails(element._id)"
|
||||
:class="{ 'cursor-pointer line-active': interactive }">
|
||||
|
||||
<div class="absolute rounded-sm w-full h-full bg-[#92abcf38]"
|
||||
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
||||
|
||||
<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"
|
||||
: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">
|
||||
{{ elementTextTransformer?.(element._id) || element._id }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-text font-semibold text-[.9rem] md:text-[1rem] manrope"> {{
|
||||
formatNumberK(element.count) }} </div>
|
||||
</div>
|
||||
<div v-if="props.data.length == 0" class="flex justify-center text-text-sub font-bold text-[1.1rem]">
|
||||
No visits yet
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 ">
|
||||
<div @click="$emit('showMore')"
|
||||
class="poppins hover:bg-black cursor-pointer w-fit px-6 py-1 rounded-lg border-[1px] border-text-sub text-[.9rem]">
|
||||
Show more
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading"
|
||||
class="backdrop-blur-[1px] z-[20] left-0 top-0 w-full h-full flex items-center justify-center font-bold rockmann absolute">
|
||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||
</div>
|
||||
</LyxUiCard>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.line-active:hover {
|
||||
.absolute {
|
||||
@apply bg-accent/20
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ui-font {
|
||||
font-feature-settings: normal;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
font-variation-settings: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4
|
||||
}
|
||||
</style>
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const browsersData = useFetch(`/api/metrics/${activeProject.value?._id}/data/browsers`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
|
||||
function showMore() {
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = browsersData.data.value || [];
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
browsersData.execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="browsersData.refresh()"
|
||||
:data="browsersData.data.value || []" desc="The browsers most used to search your website."
|
||||
:dataIcons="false" :loading="browsersData.pending.value" label="Top Browsers" sub-label="Browsers">
|
||||
</DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const devicesData = useFetch(`/api/metrics/${activeProject.value?._id}/data/devices`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
|
||||
function showMore() {
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = devicesData.data.value || [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
devicesData.execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="devicesData.refresh()" :data="devicesData.data.value || []" :dataIcons="false"
|
||||
desc="The devices most used to access your website." :loading="devicesData.pending.value" label="Top Devices"
|
||||
sub-label="Devices"></DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,51 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function goToView() {
|
||||
router.push('/dashboard/events');
|
||||
}
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const eventsData = useFetch(`/api/metrics/${activeProject.value?._id}/data/events`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
function showMore() {
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = eventsData.data.value || [];
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
eventsData.execute();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<DashboardBarsCard @showMore="showMore()" @showRawData="goToView()"
|
||||
desc="Most frequent user events triggered in this project" @dataReload="eventsData.refresh()"
|
||||
:data="eventsData.data.value || []" :loading="eventsData.pending.value" label="Top Events"
|
||||
sub-label="Events" :rawButton="!isLiveDemo()"></DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,63 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { IconProvider } from './BarsCard.vue';
|
||||
|
||||
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 activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const geolocationData = useFetch(`/api/metrics/${activeProject.value?._id}/data/countries`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
function showMore() {
|
||||
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
|
||||
dialogBarData.value = geolocationData.data.value?.map(e => {
|
||||
return { ...e, icon: iconProvider(e._id) }
|
||||
}) || [];
|
||||
isDataLoading.value = false;
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
geolocationData.execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="geolocationData.refresh()" :data="geolocationData.data.value || []" :dataIcons="false"
|
||||
:loading="geolocationData.pending.value" label="Top Countries" sub-label="Countries" :iconProvider="iconProvider"
|
||||
:customIconStyle="customIconStyle" desc=" Lists the countries where users access your website.">
|
||||
</DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,44 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const ossData = useFetch(`/api/metrics/${activeProject.value?._id}/data/oss`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
|
||||
function showMore() {
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = ossData.data.value || [];
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
ossData.execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="ossData.refresh()" :data="ossData.data.value || []"
|
||||
desc="The operating systems most commonly used by your website's visitors." :dataIcons="false"
|
||||
:loading="ossData.pending.value" label="Top OS" sub-label="OSs"></DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,68 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { IconProvider } from './BarsCard.vue';
|
||||
import ReferrerBarChart from '../referrer/ReferrerBarChart.vue';
|
||||
|
||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
||||
return ['img', `https://s2.googleusercontent.com/s2/favicons?domain=${id}&sz=64`]
|
||||
}
|
||||
|
||||
function elementTextTransformer(element: string) {
|
||||
if (element === 'self') return 'Direct Link';
|
||||
return element;
|
||||
}
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const referrersData = useFetch(`/api/metrics/${activeProject.value?._id}/data/referrers`, {
|
||||
method: 'POST', headers, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
// const customDialog = useCustomDialog();
|
||||
|
||||
// function onShowDetails(referrer: string) {
|
||||
// customDialog.openDialog(ReferrerBarChart, { slice: 'day', referrer });
|
||||
// }
|
||||
|
||||
function showMore() {
|
||||
|
||||
isShowMore.value = true;
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = referrersData.data.value?.map(e => {
|
||||
return { ...e, icon: iconProvider(e._id) }
|
||||
}) || [];
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
referrersData.execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()"
|
||||
:elementTextTransformer="elementTextTransformer" :iconProvider="iconProvider"
|
||||
@dataReload="referrersData.refresh()" :showLink=true :data="referrersData.data.value || []"
|
||||
:interactive="false" desc="Where users find your website." :dataIcons="true" :loading="referrersData.pending.value"
|
||||
label="Top Referrers" sub-label="Referrers"></DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,81 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { VisitsWebsiteAggregated } from '~/server/api/metrics/[project_id]/data/websites';
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { safeSnapshotDates } = useSnapshot()
|
||||
|
||||
const isShowMore = ref<boolean>(false);
|
||||
|
||||
const currentWebsite = ref<string>("");
|
||||
|
||||
const websitesHeaders = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10'
|
||||
}
|
||||
});
|
||||
|
||||
const pagesHeaders = computed(() => {
|
||||
return {
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to,
|
||||
Authorization: authorizationHeaderComputed.value,
|
||||
limit: isShowMore.value === true ? '200' : '10',
|
||||
'x-website-name': currentWebsite.value
|
||||
}
|
||||
});
|
||||
|
||||
const websitesData = useFetch(`/api/metrics/${activeProject.value?._id}/data/websites`, {
|
||||
method: 'POST', headers: websitesHeaders, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const pagesData = useFetch(`/api/metrics/${activeProject.value?._id}/data/pages`, {
|
||||
method: 'POST', headers: pagesHeaders, lazy: true, immediate: false
|
||||
});
|
||||
|
||||
const isPagesView = ref<boolean>(false);
|
||||
|
||||
const currentData = computed(() => {
|
||||
return isPagesView.value ? pagesData : websitesData
|
||||
})
|
||||
|
||||
|
||||
async function showDetails(website: string) {
|
||||
currentWebsite.value = website;
|
||||
pagesData.execute();
|
||||
isPagesView.value = true;
|
||||
}
|
||||
|
||||
async function showGeneral() {
|
||||
websitesData.execute();
|
||||
isPagesView.value = false;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function goToView() {
|
||||
router.push('/dashboard/visits');
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
websitesData.execute();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<DashboardBarsCard :hideShowMore="true" @showGeneral="showGeneral()" @showRawData="goToView()"
|
||||
@dataReload="currentData.refresh()" @showDetails="showDetails" :data="currentData.data.value || []"
|
||||
:loading="currentData.pending.value" :label="isPagesView ? 'Top pages' : 'Top Websites'"
|
||||
:sub-label="isPagesView ? 'Page' : 'Website'"
|
||||
:desc="isPagesView ? 'Most visited pages' : 'Most visited website in this project'"
|
||||
:interactive="!isPagesView" :rawButton="!isLiveDemo()" :isDetailView="isPagesView">
|
||||
</DashboardBarsCard>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user