fix reactivity

This commit is contained in:
Emily
2024-08-07 02:11:35 +02:00
parent 02db836003
commit 0c8ec73722
5 changed files with 140 additions and 87 deletions

View File

@@ -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,45 +56,53 @@ 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[]) {
return `${e._id}`;
});
chartData.value.datasets[0].data = resData.value.map(e => e.count);
doughnutChartRef.value?.update();
if (window.innerWidth < 800) {
if (chartOptions.value?.plugins?.legend?.display) {
chartOptions.value.plugins.legend.display = false;
}
}
chartData.value.labels = input.map(e => {
return `${e._id}`;
}); });
}) chartData.value.datasets[0].data = input.map(e => e.count);
doughnutChartRef.value?.update();
if (window.innerWidth < 800) {
if (chartOptions.value?.plugins?.legend?.display) {
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>

View File

@@ -2,73 +2,75 @@
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({
data: input,
const fixed = fixMetrics({ from: safeSnapshotDates.value.from,
data:resData.value, to: safeSnapshotDates.value.to
from: safeSnapshotDates.value.from, }, slice.value, {
to: safeSnapshotDates.value.to advanced: true,
}, slice.value, { advancedGroupKey: 'name'
advanced: true,
advancedGroupKey: 'name'
});
const parsedDatasets: any[] = [];
const colors = ['#5655d0', '#6bbbe3', '#a6d5cb', '#fae0b9'];
for (let i = 0; i < fixed.allKeys.length; i++) {
const line: any = {
data: [],
color: colors[i] || '#FF0000',
label: fixed.allKeys[i]
};
parsedDatasets.push(line)
fixed.data.forEach((e: { key: string, value: number }[]) => {
const target = e.find(e => e.key == fixed.allKeys[i]);
if (!target) return;
line.data.push(target.value);
});
}
datasets.value = parsedDatasets;
labels.value = fixed.labels;
ready.value = true;
}); });
}) const parsedDatasets: any[] = [];
const colors = ['#5655d0', '#6bbbe3', '#a6d5cb', '#fae0b9'];
for (let i = 0; i < fixed.allKeys.length; i++) {
const line: any = {
data: [],
color: colors[i] || '#FF0000',
label: fixed.allKeys[i]
};
parsedDatasets.push(line)
fixed.data.forEach((e: { key: string, value: number }[]) => {
const target = e.find(e => e.key == fixed.allKeys[i]);
if (!target) return;
line.data.push(target.value);
});
}
return {
datasets: parsedDatasets,
labels: fixed.labels
}
}
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse
});
const chartVisible = computed(() => { onMounted(async () => {
if (res.pending.value) return false; eventsStackedData.execute();
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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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' },