mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
288 lines
8.3 KiB
Vue
288 lines
8.3 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 } 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 } from "@/packages/api/src";
|
|
|
|
use([
|
|
CanvasRenderer,
|
|
BarChart,
|
|
TitleComponent,
|
|
GridComponent,
|
|
TooltipComponent,
|
|
LegendComponent
|
|
]);
|
|
|
|
provide(THEME_KEY, "dark");
|
|
const accentColor = useCssVar("--color-accent-quaternary");
|
|
|
|
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 markLineColor = useCssVar("--color-border-secondary");
|
|
|
|
|
|
// Get the organization ID using the utility function
|
|
const organizationId = computed(() => getCurrentOrganizationId());
|
|
|
|
|
|
// 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 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"
|
|
},
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
|
|
</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) : '--'" />
|
|
<StatCard
|
|
title="Billable Time"
|
|
:value="
|
|
totalWeeklyBillableTime ?
|
|
formatHumanReadableDuration(totalWeeklyBillableTime) : '--'
|
|
" />
|
|
<StatCard
|
|
title="Billable Amount"
|
|
:value="
|
|
totalWeeklyBillableAmount ?
|
|
formatCents(
|
|
totalWeeklyBillableAmount.value,
|
|
getOrganizationCurrencyString()
|
|
) : '--'
|
|
" />
|
|
<ProjectsChartCard
|
|
v-if="weeklyProjectOverview"
|
|
:weekly-project-overview="
|
|
weeklyProjectOverview
|
|
"></ProjectsChartCard>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chart {
|
|
height: 280px;
|
|
background: transparent;
|
|
}
|
|
</style>
|