mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
fix reactivity
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Chart, registerables, type ChartData, type ChartOptions } from 'chart.js';
|
import { Chart, registerables, type ChartData, type ChartOptions } from 'chart.js';
|
||||||
import { DoughnutChart, useDoughnutChart } from 'vue-chart-3';
|
import { DoughnutChart, useDoughnutChart } from 'vue-chart-3';
|
||||||
import type { EventsPie } from '~/server/api/metrics/[project_id]/events_pie';
|
import type { CustomEventsAggregated } from '~/server/api/metrics/[project_id]/data/events';
|
||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
@@ -20,15 +20,6 @@ const chartOptions = ref<ChartOptions<'doughnut'>>({
|
|||||||
ticks: { display: false },
|
ticks: { display: false },
|
||||||
grid: { display: false, drawBorder: false },
|
grid: { display: false, drawBorder: false },
|
||||||
},
|
},
|
||||||
// r: {
|
|
||||||
// ticks: { display: false },
|
|
||||||
// grid: {
|
|
||||||
// display: true,
|
|
||||||
// drawBorder: false,
|
|
||||||
// color: '#CCCCCC22',
|
|
||||||
// borderDash: [20, 8]
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
@@ -65,19 +56,18 @@ const chartData = ref<ChartData<'doughnut'>>({
|
|||||||
|
|
||||||
const { doughnutChartProps, doughnutChartRef } = useDoughnutChart({ chartData: chartData, options: chartOptions });
|
const { doughnutChartProps, doughnutChartRef } = useDoughnutChart({ chartData: chartData, options: chartOptions });
|
||||||
|
|
||||||
|
const activeProject = useActiveProject();
|
||||||
|
|
||||||
const res = useEventsData();
|
const { safeSnapshotDates } = useSnapshot();
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
|
|
||||||
res.onResponse(resData => {
|
|
||||||
if (!resData.value) return;
|
|
||||||
|
|
||||||
chartData.value.labels = resData.value.map(e => {
|
function transformResponse(input: CustomEventsAggregated[]) {
|
||||||
|
|
||||||
|
chartData.value.labels = input.map(e => {
|
||||||
return `${e._id}`;
|
return `${e._id}`;
|
||||||
});
|
});
|
||||||
|
chartData.value.datasets[0].data = input.map(e => e.count);
|
||||||
chartData.value.datasets[0].data = resData.value.map(e => e.count);
|
|
||||||
doughnutChartRef.value?.update();
|
doughnutChartRef.value?.update();
|
||||||
|
|
||||||
if (window.innerWidth < 800) {
|
if (window.innerWidth < 800) {
|
||||||
@@ -85,25 +75,34 @@ onMounted(async () => {
|
|||||||
chartOptions.value.plugins.legend.display = false;
|
chartOptions.value.plugins.legend.display = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = computed(() => {
|
||||||
|
return {
|
||||||
|
'x-from': safeSnapshotDates.value.from,
|
||||||
|
'x-to': safeSnapshotDates.value.to,
|
||||||
|
Authorization: authorizationHeaderComputed.value,
|
||||||
|
limit: "10"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
|
||||||
|
|
||||||
|
const eventsData = useFetch(`/api/metrics/${activeProject.value?._id}/data/events`, {
|
||||||
|
method: 'POST', headers, lazy: true, immediate: false,transform:transformResponse
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
eventsData.execute();
|
||||||
|
});
|
||||||
|
|
||||||
const chartVisible = computed(() => {
|
|
||||||
if (res.pending.value) return false;
|
|
||||||
if (!res.data.value) return false;
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!chartVisible" class="flex justify-center py-40">
|
<div v-if="eventsData.pending.value" class="flex justify-center py-40">
|
||||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<DoughnutChart v-if="chartVisible" v-bind="doughnutChartProps"> </DoughnutChart>
|
<DoughnutChart v-if="!eventsData.pending.value" v-bind="doughnutChartProps"> </DoughnutChart>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,27 +2,27 @@
|
|||||||
import type { Slice } from '@services/DateService';
|
import type { Slice } from '@services/DateService';
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
const datasets = ref<any[]>([]);
|
|
||||||
const labels = ref<string[]>([]);
|
|
||||||
const ready = ref<boolean>(false);
|
|
||||||
const props = defineProps<{ slice: Slice }>();
|
|
||||||
|
|
||||||
|
const props = defineProps<{ slice: Slice }>();
|
||||||
const slice = computed(() => props.slice);
|
const slice = computed(() => props.slice);
|
||||||
|
|
||||||
const res = useEventsStackedTimeline(slice);
|
const activeProject = useActiveProject();
|
||||||
|
|
||||||
const { safeSnapshotDates } = useSnapshot()
|
const { safeSnapshotDates } = useSnapshot()
|
||||||
|
|
||||||
onMounted(async () => {
|
const body = computed(() => {
|
||||||
|
return {
|
||||||
|
from: safeSnapshotDates.value.from,
|
||||||
|
to: safeSnapshotDates.value.to,
|
||||||
|
slice: slice.value,
|
||||||
|
Authorization: authorizationHeaderComputed.value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
res.execute();
|
|
||||||
|
|
||||||
res.onResponse(resData => {
|
function transformResponse(input: { _id: string, name: string, count: number }[]) {
|
||||||
|
|
||||||
if (!resData.value) return;
|
|
||||||
|
|
||||||
const fixed = fixMetrics({
|
const fixed = fixMetrics({
|
||||||
data:resData.value,
|
data: input,
|
||||||
from: safeSnapshotDates.value.from,
|
from: safeSnapshotDates.value.from,
|
||||||
to: safeSnapshotDates.value.to
|
to: safeSnapshotDates.value.to
|
||||||
}, slice.value, {
|
}, slice.value, {
|
||||||
@@ -47,28 +47,30 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
datasets.value = parsedDatasets;
|
return {
|
||||||
labels.value = fixed.labels;
|
datasets: parsedDatasets,
|
||||||
ready.value = true;
|
labels: fixed.labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
|
||||||
|
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
const chartVisible = computed(() => {
|
eventsStackedData.execute();
|
||||||
if (res.pending.value) return false;
|
});
|
||||||
if (!res.data.value) return false;
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!chartVisible" class="flex justify-center py-40">
|
<div v-if="eventsStackedData.pending.value" class="flex justify-center py-40">
|
||||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<AdvancedStackedBarChart v-if="chartVisible" :datasets="datasets" :labels="labels">
|
<AdvancedStackedBarChart v-if="!eventsStackedData.pending.value" :datasets="eventsStackedData.data.value?.datasets || []"
|
||||||
|
:labels="eventsStackedData.data.value?.labels || []">
|
||||||
</AdvancedStackedBarChart>
|
</AdvancedStackedBarChart>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -32,6 +32,36 @@ async function changeProjectName() {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteProject() {
|
||||||
|
if (!activeProject.value) return;
|
||||||
|
const sure = confirm(`Are you sure to delete the project ${activeProject.value.name} ?`);
|
||||||
|
if (!sure) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
await $fetch('/api/project/delete', {
|
||||||
|
method: 'DELETE',
|
||||||
|
...signHeaders({ 'Content-Type': 'application/json' }),
|
||||||
|
body: JSON.stringify({ project_id: activeProject.value._id.toString() })
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectsList = useProjectsList()
|
||||||
|
await projectsList.refresh();
|
||||||
|
|
||||||
|
const firstProjectId = projectsList.data.value?.[0]?._id.toString();
|
||||||
|
if (firstProjectId) {
|
||||||
|
await setActiveProject(firstProjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -62,7 +92,7 @@ async function changeProjectName() {
|
|||||||
</template>
|
</template>
|
||||||
<template #pdelete>
|
<template #pdelete>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<LyxUiButton type="danger">
|
<LyxUiButton type="danger" @click="deleteProject()">
|
||||||
Delete project
|
Delete project
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ const activeProject = useActiveProject();
|
|||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
const { data: planData } = useFetch('/api/project/plan', signHeaders());
|
const { data: planData, refresh: planRefresh, pending: planPending } = useFetch('/api/project/plan', {
|
||||||
|
...signHeaders(),
|
||||||
|
lazy: true
|
||||||
|
});
|
||||||
|
|
||||||
const percent = computed(() => {
|
const percent = computed(() => {
|
||||||
if (!planData.value) return '-';
|
if (!planData.value) return '-';
|
||||||
@@ -38,7 +41,10 @@ const prettyExpireDate = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const { data: invoices } = await useFetch(`/api/pay/${activeProject.value?._id.toString()}/invoices`, signHeaders())
|
const { data: invoices, refresh: invoicesRefresh, pending: invoicesPending } = useFetch(`/api/pay/${activeProject.value?._id.toString()}/invoices`, {
|
||||||
|
...signHeaders(),
|
||||||
|
lazy: true
|
||||||
|
})
|
||||||
|
|
||||||
const showPricingDrawer = ref<boolean>(false);
|
const showPricingDrawer = ref<boolean>(false);
|
||||||
function onPlanUpgradeClick() {
|
function onPlanUpgradeClick() {
|
||||||
@@ -58,6 +64,12 @@ function getPremiumName(type: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(activeProject, () => {
|
||||||
|
invoicesRefresh();
|
||||||
|
planRefresh();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const entries: SettingsTemplateEntry[] = [
|
const entries: SettingsTemplateEntry[] = [
|
||||||
{ id: 'plan', title: 'Current plan', text: 'Manage current plat for this project' },
|
{ id: 'plan', title: 'Current plan', text: 'Manage current plat for this project' },
|
||||||
{ id: 'usage', title: 'Usage', text: 'Show usage of current project' },
|
{ id: 'usage', title: 'Usage', text: 'Show usage of current project' },
|
||||||
@@ -77,7 +89,12 @@ const entries: SettingsTemplateEntry[] = [
|
|||||||
</PricingDrawer>
|
</PricingDrawer>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<SettingsTemplate :entries="entries">
|
<div v-if="invoicesPending || planPending"
|
||||||
|
class="backdrop-blur-[1px] z-[20] mt-20 w-full h-full flex items-center justify-center font-bold">
|
||||||
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SettingsTemplate v-if="!invoicesPending && !planPending" :entries="entries">
|
||||||
<template #plan>
|
<template #plan>
|
||||||
<LyxUiCard v-if="planData" class="flex flex-col w-full">
|
<LyxUiCard v-if="planData" class="flex flex-col w-full">
|
||||||
<div class="flex flex-col gap-6 px-8 grow">
|
<div class="flex flex-col gap-6 px-8 grow">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SettingsTemplateEntry } from './Template.vue';
|
import type { SettingsTemplateEntry } from './Template.vue';
|
||||||
|
|
||||||
|
const activeProject = useActiveProject();
|
||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ const columns = [
|
|||||||
// { key: 'pending', label: 'Pending' },
|
// { key: 'pending', label: 'Pending' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { data: members, refresh: refreshMembers } = useFetch('/api/project/members/list', signHeaders());
|
const { data: members, refresh: refreshMembers, pending: pendingMembers } = useFetch('/api/project/members/list', signHeaders());
|
||||||
|
|
||||||
const showAddMember = ref<boolean>(false);
|
const showAddMember = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -70,6 +71,10 @@ async function addMember() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(activeProject, () => {
|
||||||
|
refreshMembers();
|
||||||
|
})
|
||||||
|
|
||||||
const entries: SettingsTemplateEntry[] = [
|
const entries: SettingsTemplateEntry[] = [
|
||||||
{ id: 'add', title: 'Add member', text: 'Add new member to project' },
|
{ id: 'add', title: 'Add member', text: 'Add new member to project' },
|
||||||
{ id: 'members', title: 'Members', text: 'Manage members of current project' },
|
{ id: 'members', title: 'Members', text: 'Manage members of current project' },
|
||||||
|
|||||||
Reference in New Issue
Block a user