Files
solidtime/resources/js/Components/Dashboard/ThisWeekOverview.vue
Gregor Vostrak df2fe1da1e add light mode
2025-03-28 14:54:31 +01:00

285 lines
8.4 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 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());
// 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);
}
}
}
]
};
});
</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>