This commit is contained in:
Emily
2024-09-27 20:33:49 +02:00
parent 8e3ad2920f
commit 3c77a727cd
37 changed files with 815 additions and 491752 deletions

View File

@@ -0,0 +1,157 @@
<script lang="ts" setup>
import { sub, isSameDay, type Duration } from 'date-fns'
type ChartType = 'bar' | 'line';
const chartTypeOptions: { value: ChartType, label: string }[] = [
{ value: 'bar', label: 'Bar chart' },
{ value: 'line', label: 'Line chart' },
]
type yAxisMode = 'count';
const yAxisModeOptions: { value: yAxisMode, label: string }[] = [
{ value: 'count', label: 'Count fields' },
]
type Slice = 'day' | 'month';
const sliceOptions: Slice[] = ['day', 'month'];
const chartType = ref<ChartType>('line');
const tableName = ref<string>('');
const xAxis = ref<string>('');
const yAxisMode = ref<yAxisMode>('count');
const slice = ref<Slice>('day');
const visualizationName = ref<string>('');
const ranges = [
{ label: 'Last 7 days', duration: { days: 7 } },
{ label: 'Last 14 days', duration: { days: 14 } },
{ label: 'Last 30 days', duration: { days: 30 } },
{ label: 'Last 3 months', duration: { months: 3 } },
{ label: 'Last 6 months', duration: { months: 6 } },
{ label: 'Last year', duration: { years: 1 } }
]
const timeframe = ref<{ start: Date, end: Date }>({ start: sub(new Date(), { days: 14 }), end: new Date() })
function isRangeSelected(duration: Duration) {
return isSameDay(timeframe.value.start, sub(new Date(), duration)) && isSameDay(timeframe.value.end, new Date())
}
function selectRange(duration: Duration) {
timeframe.value = { start: sub(new Date(), duration), end: new Date() }
}
const { createAlert } = useAlert();
const { closeDialog } = useCustomDialog();
const activeProjectId = useActiveProjectId();
const { integrationsCredentials,testConnection } = useSupabase();
async function generate() {
const credentials = integrationsCredentials.data.value;
if (!credentials?.supabase) return createAlert('Credentials not found', 'Please add supabase credentials on the integration page', 'far fa-error', 5000);
const connectionStatus = await testConnection();
if (!connectionStatus) return createAlert('Invalid supabase credentials', 'Please check your supabase credentials on the integration page', 'far fa-error', 5000);
try {
const creation = await $fetch('/api/integrations/supabase/add', {
...signHeaders({
'x-pid': activeProjectId.data.value || '',
'Content-Type': 'application/json'
}),
method: 'POST',
body: JSON.stringify({
name: visualizationName.value,
chart_type: chartType.value,
table_name: tableName.value,
xField: xAxis.value,
yMode: yAxisMode.value,
from: timeframe.value.start,
to: timeframe.value.end,
slice: slice.value
})
})
createAlert('Integration generated', 'Integration generated successfully', 'far fa-check-circle', 5000);
closeDialog();
} catch (ex: any) {
createAlert('Error generating integrations', ex.response._data.message.toString(), 'far fa-error', 5000);
}
}
</script>
<template>
<div class="flex flex-col gap-4">
<div>
<div> Visualization name </div>
<div>
<LyxUiInput class="w-full px-2 py-1" v-model="visualizationName"></LyxUiInput>
</div>
</div>
<div>
<div> Chart type </div>
<USelect v-model="chartType" :options="chartTypeOptions" />
</div>
<div>
<div> Table name </div>
<div>
<LyxUiInput class="w-full px-2 py-1" v-model="tableName"></LyxUiInput>
</div>
</div>
<div>
<div> X axis field </div>
<div>
<LyxUiInput class="w-full px-2 py-1" v-model="xAxis"></LyxUiInput>
</div>
</div>
<div>
<div> Y axis mode </div>
<div>
<USelect v-model="yAxisMode" :options="yAxisModeOptions" />
</div>
</div>
<div>
<div> Timeframe </div>
<div>
<UPopover class="w-full" :popper="{ placement: 'bottom' }">
<UButton class="w-full" color="primary" variant="solid">
<div class="flex items-center justify-center w-full gap-2">
<i class="i-heroicons-calendar-days-20-solid"></i>
{{ timeframe.start.toLocaleDateString() }} - {{ timeframe.end.toLocaleDateString() }}
</div>
</UButton>
<template #panel="{ close }">
<div class="flex items-center sm:divide-x divide-gray-200 dark:divide-gray-800">
<div class="hidden sm:flex flex-col py-4">
<UButton v-for="(range, index) in ranges" :key="index" :label="range.label" color="gray"
variant="ghost" class="rounded-none px-6"
:class="[isRangeSelected(range.duration) ? 'bg-gray-100 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800/50']"
truncate @click="selectRange(range.duration)" />
</div>
<DatePicker v-model="timeframe" @close="close" />
</div>
</template>
</UPopover>
</div>
</div>
<div>
<div> View mode </div>
<div>
<USelect v-model="slice" :options="sliceOptions" />
</div>
</div>
<LyxUiButton type="primary" @click="generate()">
Generate
</LyxUiButton>
</div>
</template>

View File

@@ -0,0 +1,170 @@
<script setup lang="ts">
import type { TSupabaseIntegration } from '@schema/integrations/SupabaseIntegrationSchema';
import type { ChartData, ChartOptions } from 'chart.js';
import { useLineChart, LineChart } from 'vue-chart-3';
const props = defineProps<{ integration_id: string }>();
const activeProjectId = useActiveProjectId();
const supabaseData = ref<{ labels: string[], data: number[] }>();
const supabaseError = ref<string | undefined>(undefined);
const supabaseFetching = ref<boolean>(false);
const { getRemoteData } = useSupabase();
function createGradient() {
const c = document.createElement('canvas');
const ctx = c.getContext("2d");
let gradient: any = `#34B67C22`;
if (ctx) {
gradient = ctx.createLinearGradient(0, 25, 0, 300);
gradient.addColorStop(0, `#34B67C99`);
gradient.addColorStop(0.35, `#34B67C66`);
gradient.addColorStop(1, `#34B67C22`);
} else {
console.warn('Cannot get context for gradient');
}
chartData.value.datasets[0].backgroundColor = [gradient];
}
const chartOptions = ref<ChartOptions<'line'>>({
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x',
includeInvisible: true
},
scales: {
y: {
ticks: { display: true },
grid: {
display: true,
drawBorder: false,
color: '#CCCCCC22',
// borderDash: [5, 10]
},
},
x: {
ticks: { display: true },
grid: {
display: true,
drawBorder: false,
color: '#CCCCCC22',
}
}
},
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
enabled: true,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleFont: { size: 16, weight: 'bold' },
bodyFont: { size: 14 },
padding: 10,
cornerRadius: 4,
boxPadding: 10,
caretPadding: 20,
yAlign: 'bottom',
xAlign: 'center',
}
},
});
const chartData = ref<ChartData<'line'>>({
labels: [],
datasets: [
{
data: [],
backgroundColor: ['#34B67C' + '77'],
borderColor: '#34B67C',
borderWidth: 4,
fill: true,
tension: 0.45,
pointRadius: 0,
pointHoverRadius: 10,
hoverBackgroundColor: '#34B67C',
hoverBorderColor: 'white',
hoverBorderWidth: 2,
},
],
});
onMounted(async () => {
supabaseFetching.value = true;
supabaseError.value = undefined;
const integrationData = await $fetch<TSupabaseIntegration>('/api/integrations/supabase/get', {
...signHeaders({
'x-pid': activeProjectId.data.value || '',
'x-integration': props.integration_id
})
});
if (!integrationData) {
supabaseError.value = 'Cannot get integration data';
supabaseFetching.value = false;
return;
}
try {
const data = await getRemoteData(
integrationData.table_name,
integrationData.xField,
integrationData.yMode,
integrationData.from.toString(),
integrationData.to.toString(),
integrationData.slice,
);
if (data.error) {
supabaseError.value = data.error;
supabaseFetching.value = false;
return;
}
supabaseFetching.value = false;
supabaseData.value = data.result;
chartData.value.labels = data.result?.labels || [];
chartData.value.datasets[0].data = data.result?.data || [];
console.log(data.result);
createGradient();
} catch (ex: any) {
if (!ex.response._data) {
supabaseError.value = ex.message.toString();
supabaseFetching.value = false;
} else {
supabaseError.value = ex.response._data.message.toString();
supabaseFetching.value = false;
}
}
});
const { lineChartProps, lineChartRef } = useLineChart({ chartData: chartData, options: chartOptions });
</script>
<template>
<div v-if="!supabaseFetching">
<div v-if="!supabaseError">
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
</div>
<div v-if="supabaseError"> {{ supabaseError }} </div>
</div>
<div v-if="supabaseFetching">
Getting remote data...
</div>
</template>