mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-06 20:37:32 +00:00
308 lines
9.5 KiB
Vue
308 lines
9.5 KiB
Vue
<script setup lang="ts">
|
|
import { use } from 'echarts/core';
|
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
import { BarChart } from 'echarts/charts';
|
|
import {
|
|
GridComponent,
|
|
LegendComponent,
|
|
TitleComponent,
|
|
TooltipComponent,
|
|
} from 'echarts/components';
|
|
import VChart, { THEME_KEY } from 'vue-echarts';
|
|
import { computed, provide, inject, type ComputedRef } from 'vue';
|
|
import StatCard from '@/Components/Common/StatCard.vue';
|
|
import { ClockIcon } from '@heroicons/vue/20/solid';
|
|
import CardTitle from '@/packages/ui/src/CardTitle.vue';
|
|
import LinearGradient from 'zrender/lib/graphic/LinearGradient';
|
|
import ProjectsChartCard from '@/Components/Dashboard/ProjectsChartCard.vue';
|
|
import { formatHumanReadableDuration } from '@/packages/ui/src/utils/time';
|
|
import { formatCents } from '@/packages/ui/src/utils/money';
|
|
import { getWeekStart } from '@/packages/ui/src/utils/settings';
|
|
import { useCssVar } from '@vueuse/core';
|
|
import { getOrganizationCurrencyString } from '@/utils/money';
|
|
import { useQuery } from '@tanstack/vue-query';
|
|
import { getCurrentOrganizationId } from '@/utils/useUser';
|
|
import { api, type Organization } from '@/packages/api/src';
|
|
|
|
use([
|
|
CanvasRenderer,
|
|
BarChart,
|
|
TitleComponent,
|
|
GridComponent,
|
|
TooltipComponent,
|
|
LegendComponent,
|
|
]);
|
|
|
|
provide(THEME_KEY, 'dark');
|
|
|
|
const weekdays = computed(() => {
|
|
const daysOrder = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
const dayMapping: Record<string, string> = {
|
|
monday: 'Mon',
|
|
tuesday: 'Tue',
|
|
wednesday: 'Wed',
|
|
thursday: 'Thu',
|
|
friday: 'Fri',
|
|
saturday: 'Sat',
|
|
sunday: 'Sun',
|
|
};
|
|
if (dayMapping[getWeekStart()]) {
|
|
const customOrder = [];
|
|
const startIndex = daysOrder.indexOf(dayMapping[getWeekStart()]);
|
|
|
|
for (let i = startIndex; i < 7 + startIndex; i++) {
|
|
customOrder.push(daysOrder[i % daysOrder.length]);
|
|
}
|
|
|
|
return customOrder;
|
|
} else {
|
|
return daysOrder;
|
|
}
|
|
});
|
|
|
|
const accentColor = useCssVar('--theme-color-chart', null, { observe: true });
|
|
|
|
// Get the organization ID using the utility function
|
|
const organizationId = computed(() => getCurrentOrganizationId());
|
|
|
|
const organization = inject<ComputedRef<Organization>>('organization');
|
|
|
|
// Set up the queries
|
|
const { data: weeklyProjectOverview } = useQuery({
|
|
queryKey: ['weeklyProjectOverview', organizationId],
|
|
queryFn: () => {
|
|
return api.weeklyProjectOverview({
|
|
params: {
|
|
organization: organizationId.value!,
|
|
},
|
|
});
|
|
},
|
|
enabled: computed(() => !!organizationId.value),
|
|
});
|
|
|
|
const { data: totalWeeklyTime } = useQuery({
|
|
queryKey: ['totalWeeklyTime', organizationId],
|
|
queryFn: () => {
|
|
return api.totalWeeklyTime({
|
|
params: {
|
|
organization: organizationId.value!,
|
|
},
|
|
});
|
|
},
|
|
enabled: computed(() => !!organizationId.value),
|
|
});
|
|
|
|
const { data: totalWeeklyBillableTime } = useQuery({
|
|
queryKey: ['totalWeeklyBillableTime', organizationId],
|
|
queryFn: () => {
|
|
return api.totalWeeklyBillableTime({
|
|
params: {
|
|
organization: organizationId.value!,
|
|
},
|
|
});
|
|
},
|
|
enabled: computed(() => !!organizationId.value),
|
|
});
|
|
|
|
const { data: totalWeeklyBillableAmount } = useQuery({
|
|
queryKey: ['totalWeeklyBillableAmount', organizationId],
|
|
queryFn: () => {
|
|
return api.totalWeeklyBillableAmount({
|
|
params: {
|
|
organization: organizationId.value!,
|
|
},
|
|
});
|
|
},
|
|
enabled: computed(() => !!organizationId.value),
|
|
});
|
|
|
|
const { data: weeklyHistory } = useQuery({
|
|
queryKey: ['weeklyHistory', organizationId],
|
|
queryFn: () => {
|
|
return api.weeklyHistory({
|
|
params: {
|
|
organization: organizationId.value!,
|
|
},
|
|
});
|
|
},
|
|
enabled: computed(() => !!organizationId.value),
|
|
});
|
|
|
|
const seriesData = computed(() => {
|
|
if (!weeklyHistory.value) {
|
|
return [];
|
|
}
|
|
return weeklyHistory.value?.map((el) => {
|
|
return {
|
|
value: el.duration,
|
|
...{
|
|
itemStyle: {
|
|
borderColor: new LinearGradient(0, 0, 0, 1, [
|
|
{
|
|
offset: 0,
|
|
color: 'rgba(' + accentColor.value + ',0.7)',
|
|
},
|
|
{
|
|
offset: 1,
|
|
color: 'rgba(' + accentColor.value + ',0.5)',
|
|
},
|
|
]),
|
|
emphasis: {
|
|
color: new LinearGradient(0, 0, 0, 1, [
|
|
{
|
|
offset: 0,
|
|
color: 'rgba(' + accentColor.value + ',0.9)',
|
|
},
|
|
{
|
|
offset: 1,
|
|
color: 'rgba(' + accentColor.value + ',0.7)',
|
|
},
|
|
]),
|
|
},
|
|
borderRadius: [12, 12, 0, 0],
|
|
color: new LinearGradient(0, 0, 0, 1, [
|
|
{
|
|
offset: 0,
|
|
color: 'rgba(' + accentColor.value + ',0.7)',
|
|
},
|
|
{
|
|
offset: 1,
|
|
color: 'rgba(' + accentColor.value + ',0.5)',
|
|
},
|
|
]),
|
|
},
|
|
},
|
|
};
|
|
});
|
|
});
|
|
|
|
const markLineColor = useCssVar('--color-border-secondary', null, {
|
|
observe: true,
|
|
});
|
|
const labelColor = useCssVar('--color-text-secondary', null, { observe: true });
|
|
const option = computed(() => {
|
|
return {
|
|
tooltip: {
|
|
trigger: 'item',
|
|
},
|
|
grid: {
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 50,
|
|
left: 0,
|
|
},
|
|
backgroundColor: 'transparent',
|
|
xAxis: {
|
|
type: 'category',
|
|
data: weekdays.value,
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: 'transparent', // Set desired color here
|
|
},
|
|
},
|
|
axisLabel: {
|
|
fontSize: 16,
|
|
fontWeight: 600,
|
|
margin: 24,
|
|
fontFamily: 'Outfit, sans-serif',
|
|
color: labelColor.value,
|
|
},
|
|
axisTick: {
|
|
lineStyle: {
|
|
color: 'transparent', // Set desired color here
|
|
},
|
|
},
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
splitLine: {
|
|
lineStyle: {
|
|
color: markLineColor.value,
|
|
},
|
|
},
|
|
},
|
|
series: [
|
|
{
|
|
data: seriesData.value,
|
|
type: 'bar',
|
|
tooltip: {
|
|
valueFormatter: (value: number) => {
|
|
return formatHumanReadableDuration(
|
|
value,
|
|
organization?.value?.interval_format,
|
|
organization?.value?.number_format
|
|
);
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="grid space-y-5 sm:space-y-0 sm:gap-x-6 xl:gap-x-6 grid-cols-1 lg:grid-cols-3 xl:grid-cols-4">
|
|
<div class="col-span-2 xl:col-span-3">
|
|
<CardTitle
|
|
title="This Week"
|
|
class="pb-8"
|
|
:icon="ClockIcon"></CardTitle>
|
|
<v-chart
|
|
v-if="weeklyHistory"
|
|
:autoresize="true"
|
|
class="chart"
|
|
:option="option" />
|
|
</div>
|
|
<div class="space-y-6">
|
|
<StatCard
|
|
title="Spent Time"
|
|
:value="
|
|
totalWeeklyTime
|
|
? formatHumanReadableDuration(
|
|
totalWeeklyTime,
|
|
organization?.interval_format,
|
|
organization?.number_format
|
|
)
|
|
: '--'
|
|
" />
|
|
<StatCard
|
|
title="Billable Time"
|
|
:value="
|
|
totalWeeklyBillableTime
|
|
? formatHumanReadableDuration(
|
|
totalWeeklyBillableTime,
|
|
organization?.interval_format,
|
|
organization?.number_format
|
|
)
|
|
: '--'
|
|
" />
|
|
<StatCard
|
|
title="Billable Amount"
|
|
:value="
|
|
totalWeeklyBillableAmount
|
|
? formatCents(
|
|
totalWeeklyBillableAmount.value,
|
|
getOrganizationCurrencyString(),
|
|
organization?.currency_format,
|
|
organization?.currency_symbol,
|
|
organization?.number_format
|
|
)
|
|
: '--'
|
|
" />
|
|
<ProjectsChartCard
|
|
v-if="weeklyProjectOverview"
|
|
:weekly-project-overview="
|
|
weeklyProjectOverview
|
|
"></ProjectsChartCard>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chart {
|
|
height: 280px;
|
|
background: transparent;
|
|
}
|
|
</style>
|