add format check, update prettier rules, apply rules consistently

This commit is contained in:
Gregor Vostrak
2025-07-29 18:58:31 +02:00
parent b11672732b
commit a3d7cdaf4d
323 changed files with 3728 additions and 5579 deletions

View File

@@ -55,10 +55,7 @@ provide(THEME_KEY, 'dark');
const max = computed(() => {
if (!isLoading.value && dailyHoursTracked.value) {
return Math.max(
Math.max(...dailyHoursTracked.value.map((el) => el.duration)),
1
);
return Math.max(Math.max(...dailyHoursTracked.value.map((el) => el.duration)), 1);
} else {
return 1;
}
@@ -97,10 +94,7 @@ const option = computed(() => {
},
range: [
dayjs().format('YYYY-MM-DD'),
getDayJsInstance()()
.subtract(50, 'day')
.startOf('week')
.format('YYYY-MM-DD'),
getDayJsInstance()().subtract(50, 'day').startOf('week').format('YYYY-MM-DD'),
],
itemStyle: {
color: 'transparent',
@@ -112,9 +106,7 @@ const option = computed(() => {
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data:
dailyHoursTracked?.value?.map((el) => [el.date, el.duration]) ??
[],
data: dailyHoursTracked?.value?.map((el) => [el.date, el.duration]) ?? [],
itemStyle: {
borderRadius: 5,
borderColor: borderColor.value,
@@ -159,9 +151,7 @@ const option = computed(() => {
:option="option"
style="height: 260px; background-color: transparent" />
</div>
<div v-else class="text-center text-gray-500 py-8">
No activity data available
</div>
<div v-else class="text-center text-gray-500 py-8">No activity data available</div>
</div>
</DashboardCard>
</template>

View File

@@ -10,19 +10,21 @@ const props = defineProps<{
const accentColor = useCssVariable('--theme-color-chart');
const markLineColor = useCssVariable('--color-border-secondary');
const seriesData = computed(() => props.history.map((el) => {
return {
value: el,
...{
itemStyle: {
borderWidth: 1,
borderColor: 'rgba(' + accentColor.value + ',0.8)',
borderRadius: [2, 2, 0, 0],
color: 'rgba(' + accentColor.value + ',0.8)',
const seriesData = computed(() =>
props.history.map((el) => {
return {
value: el,
...{
itemStyle: {
borderWidth: 1,
borderColor: 'rgba(' + accentColor.value + ',0.8)',
borderRadius: [2, 2, 0, 0],
color: 'rgba(' + accentColor.value + ',0.8)',
},
},
},
};
}));
};
})
);
const option = computed(() => ({
grid: {
top: 0,

View File

@@ -1,9 +1,6 @@
<script setup lang="ts">
import DayOverviewCardChart from '@/Components/Dashboard/DayOverviewCardChart.vue';
import {
formatHumanReadableDate,
formatHumanReadableDuration,
} from '@/packages/ui/src/utils/time';
import { formatHumanReadableDate, formatHumanReadableDuration } from '@/packages/ui/src/utils/time';
import { inject, type ComputedRef } from 'vue';
import type { Organization } from '@/packages/api/src';
@@ -17,8 +14,7 @@ defineProps<{
</script>
<template>
<div
class="px-3.5 py-2 flex justify-between @container border-b border-b-background-separator">
<div class="px-3.5 py-2 flex justify-between @container border-b border-b-background-separator">
<div class="flex items-center min-w-[70px]">
<p class="font-medium text-sm text-text-primary">
{{ formatHumanReadableDate(date) }}

View File

@@ -1,35 +1,33 @@
<script setup lang="ts">
import { useQuery } from "@tanstack/vue-query";
import { computed } from "vue";
import DashboardCard from "@/Components/Dashboard/DashboardCard.vue";
import DayOverviewCardEntry from "@/Components/Dashboard/DayOverviewCardEntry.vue";
import { CalendarIcon } from "@heroicons/vue/20/solid";
import { getCurrentOrganizationId } from "@/utils/useUser";
import { api } from "@/packages/api/src";
import { LoadingSpinner } from "@/packages/ui/src";
import { useQuery } from '@tanstack/vue-query';
import { computed } from 'vue';
import DashboardCard from '@/Components/Dashboard/DashboardCard.vue';
import DayOverviewCardEntry from '@/Components/Dashboard/DayOverviewCardEntry.vue';
import { CalendarIcon } from '@heroicons/vue/20/solid';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '@/packages/api/src';
import { LoadingSpinner } from '@/packages/ui/src';
// Get the organization ID using the utility function
const organizationId = computed(() => getCurrentOrganizationId());
// Set up the query
const { data: last7Days, isLoading } = useQuery({
queryKey: ["lastSevenDays", organizationId],
queryKey: ['lastSevenDays', organizationId],
queryFn: () => {
return api.lastSevenDays({
params: {
organization: organizationId.value!
}
organization: organizationId.value!,
},
});
},
enabled: computed(() => !!organizationId.value),
placeholderData: Array.from({ length: 7 }, (_, i) => ({
date: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
date: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
duration: 0,
history: Array(8).fill(0)
}))
history: Array(8).fill(0),
})),
});
</script>
<template>
@@ -46,8 +44,6 @@ const { data: last7Days, isLoading } = useQuery({
:history="day.history"
:duration="day.duration"></DayOverviewCardEntry>
</div>
<div v-else class="text-center text-gray-500 py-8">
No data available
</div>
<div v-else class="text-center text-gray-500 py-8">No data available</div>
</DashboardCard>
</template>

View File

@@ -12,16 +12,9 @@ import {
} from 'echarts/components';
import { formatHumanReadableDuration } from '@/packages/ui/src/utils/time';
import { useCssVariable } from '@/utils/useCssVariable';
import type { Organization } from "@/packages/api/src";
import type { Organization } from '@/packages/api/src';
use([
CanvasRenderer,
PieChart,
TitleComponent,
GridComponent,
TooltipComponent,
LegendComponent,
]);
use([CanvasRenderer, PieChart, TitleComponent, GridComponent, TooltipComponent, LegendComponent]);
provide(THEME_KEY, 'dark');
const labelColor = useCssVariable('--color-text-secondary');
@@ -72,7 +65,11 @@ const option = computed(() => ({
},
tooltip: {
valueFormatter: (value: number) => {
return formatHumanReadableDuration(value, organization?.value?.interval_format, organization?.value?.number_format);
return formatHumanReadableDuration(
value,
organization?.value?.interval_format,
organization?.value?.number_format
);
},
},
data: seriesData,

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import { useQuery } from "@tanstack/vue-query";
import { computed } from "vue";
import RecentlyTrackedTasksCardEntry from "@/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue";
import DashboardCard from "@/Components/Dashboard/DashboardCard.vue";
import { CheckCircleIcon } from "@heroicons/vue/20/solid";
import SecondaryButton from "@/packages/ui/src/Buttons/SecondaryButton.vue";
import { PlusCircleIcon } from "@heroicons/vue/24/solid";
import { router } from "@inertiajs/vue3";
import { getCurrentMembershipId, getCurrentOrganizationId } from "@/utils/useUser";
import { api } from "@/packages/api/src";
import { LoadingSpinner } from "@/packages/ui/src";
import { useQuery } from '@tanstack/vue-query';
import { computed } from 'vue';
import RecentlyTrackedTasksCardEntry from '@/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue';
import DashboardCard from '@/Components/Dashboard/DashboardCard.vue';
import { CheckCircleIcon } from '@heroicons/vue/20/solid';
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import { PlusCircleIcon } from '@heroicons/vue/24/solid';
import { router } from '@inertiajs/vue3';
import { getCurrentMembershipId, getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '@/packages/api/src';
import { LoadingSpinner } from '@/packages/ui/src';
// Get the organization ID using the utility function
const organizationId = computed(() => getCurrentOrganizationId());
@@ -17,19 +17,23 @@ const organizationId = computed(() => getCurrentOrganizationId());
// Function to fetch latest tasks using the API client
// Set up the query
const { data: timeEntriesResponse, isLoading, refetch } = useQuery({
queryKey: ["timeEntries", organizationId],
const {
data: timeEntriesResponse,
isLoading,
refetch,
} = useQuery({
queryKey: ['timeEntries', organizationId],
queryFn: () => {
return api.getTimeEntries({
params: {
organization: organizationId.value!
organization: organizationId.value!,
},
queries: {
member_id: getCurrentMembershipId()
}
member_id: getCurrentMembershipId(),
},
});
},
enabled: computed(() => !!organizationId.value)
enabled: computed(() => !!organizationId.value),
});
const latestTasks = computed(() => {
@@ -45,21 +49,26 @@ const filteredLatestTasks = computed(() => {
const finishedTimeEntries = latestTasks.value.filter((item) => item.end !== null);
// filter out duplicates based on description, task, project, tags and billable
return finishedTimeEntries.filter((item, index, self) => {
return index === self.findIndex((t) => (
t.description === item.description &&
t.task_id === item.task_id &&
t.project_id === item.project_id &&
t.tags.length === item.tags.length &&
t.tags.every((tag) => item.tags.includes(tag)) &&
t.billable === item.billable
));
}).slice(0, 4);
return finishedTimeEntries
.filter((item, index, self) => {
return (
index ===
self.findIndex(
(t) =>
t.description === item.description &&
t.task_id === item.task_id &&
t.project_id === item.project_id &&
t.tags.length === item.tags.length &&
t.tags.every((tag) => item.tags.includes(tag)) &&
t.billable === item.billable
)
);
})
.slice(0, 4);
});
// Listen for dashboard refresh events
window.addEventListener("dashboard:refresh", () => {
window.addEventListener('dashboard:refresh', () => {
refetch();
});
</script>
@@ -74,20 +83,17 @@ window.addEventListener("dashboard:refresh", () => {
v-for="lastTask in filteredLatestTasks"
:key="lastTask.id"
:time-entry="lastTask"
:class="filteredLatestTasks.length === 4 ? 'last:border-0' : ''"></RecentlyTrackedTasksCardEntry>
:class="
filteredLatestTasks.length === 4 ? 'last:border-0' : ''
"></RecentlyTrackedTasksCardEntry>
</div>
<div
v-else
class="text-center flex flex-1 justify-center items-center">
<div v-else class="text-center flex flex-1 justify-center items-center">
<div>
<PlusCircleIcon
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<h3 class="text-text-primary font-semibold text-sm">
No recent tasks found
</h3>
<PlusCircleIcon class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<h3 class="text-text-primary font-semibold text-sm">No recent tasks found</h3>
<p class="pb-5 text-sm">Create tasks inside of a project!</p>
<SecondaryButton @click="router.visit(route('projects'))"
>Go to Projects
>Go to Projects
</SecondaryButton>
</div>
</div>
@@ -95,12 +101,11 @@ window.addEventListener("dashboard:refresh", () => {
v-if="latestTasks && latestTasks.length === 1"
class="text-center flex flex-1 justify-center items-center text-sm">
<div>
<PlusCircleIcon
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<PlusCircleIcon class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<h3 class="text-text-primary font-semibold">Add more tasks</h3>
<p class="pb-5">Create tasks inside of a project!</p>
<SecondaryButton @click="router.visit(route('projects'))"
>Go to Projects
>Go to Projects
</SecondaryButton>
</div>
</div>

View File

@@ -6,12 +6,12 @@ import { storeToRefs } from 'pinia';
import { computed } from 'vue';
import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
import { getDayJsInstance } from '@/packages/ui/src/utils/time';
import type { TimeEntry } from "@/packages/api/src";
import { useTasksStore } from "@/utils/useTasks";
import { ChevronRightIcon } from "@heroicons/vue/16/solid";
import type { TimeEntry } from '@/packages/api/src';
import { useTasksStore } from '@/utils/useTasks';
import { ChevronRightIcon } from '@heroicons/vue/16/solid';
const props = defineProps<{
timeEntry: TimeEntry
timeEntry: TimeEntry;
}>();
const { projects } = storeToRefs(useProjectsStore());
@@ -20,7 +20,7 @@ const project = computed(() => {
return projects.value.find((project) => project.id === props.timeEntry.project_id);
});
const {tasks} = storeToRefs(useTasksStore());
const { tasks } = storeToRefs(useTasksStore());
const task = computed(() => {
return tasks.value.find((task) => task.id === props.timeEntry.task_id);
@@ -42,39 +42,31 @@ async function startTaskTimer() {
await setActiveState(true);
useCurrentTimeEntryStore().fetchCurrentTimeEntry();
}
</script>
<template>
<div
class="px-3.5 py-2 grid grid-cols-5 border-b border-b-background-separator">
<div class="px-3.5 py-2 grid grid-cols-5 border-b border-b-background-separator">
<div class="col-span-4">
<p class="font-medium text-text-primary text-sm pb-1 truncate">
<span v-if="timeEntry.description"> {{ timeEntry.description }}</span>
<span v-else class="text-text-tertiary">No description</span>
</p>
<ProjectBadge
size="base"
class="min-w-0 max-w-full"
:color="project?.color">
<ProjectBadge size="base" class="min-w-0 max-w-full" :color="project?.color">
<div class="flex items-center lg:space-x-0.5 min-w-0">
<span class="whitespace-nowrap ">
<span class="whitespace-nowrap">
{{ project?.name ?? 'No Project' }}
</span>
<ChevronRightIcon
v-if="task"
class="w-4 text-text-secondary shrink-0"></ChevronRightIcon>
<div
v-if="task"
class="min-w-0 shrink truncate">
<div v-if="task" class="min-w-0 shrink truncate">
{{ task.name }}
</div>
</div>
</ProjectBadge>
</div>
<div class="flex items-center justify-center">
<TimeTrackerStartStop
@changed="startTaskTimer"></TimeTrackerStartStop>
<TimeTrackerStartStop @changed="startTaskTimer"></TimeTrackerStartStop>
</div>
</div>
</template>

View File

@@ -7,7 +7,7 @@ import { UserGroupIcon } from '@heroicons/vue/20/solid';
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '@/packages/api/src';
import { LoadingSpinner } from "@/packages/ui/src";
import { LoadingSpinner } from '@/packages/ui/src';
import { router } from '@inertiajs/vue3';
// Get the organization ID using the utility function
@@ -15,17 +15,16 @@ const organizationId = computed(() => getCurrentOrganizationId());
// Set up the query
const { data: latestTeamActivity, isLoading } = useQuery({
queryKey: ['latestTeamActivity', organizationId],
queryFn: () => {
return api.latestTeamActivity({
params: {
organization: organizationId.value!
}
})
},
enabled: computed(() => !!organizationId.value),
queryKey: ['latestTeamActivity', organizationId],
queryFn: () => {
return api.latestTeamActivity({
params: {
organization: organizationId.value!,
},
});
},
enabled: computed(() => !!organizationId.value),
});
</script>
<template>
@@ -42,18 +41,13 @@ const { data: latestTeamActivity, isLoading } = useQuery({
:description="activity.description"
:working="activity.status"></TeamActivityCardEntry>
</div>
<div v-else class="text-center text-gray-500 py-8">
No team activity found
</div>
<div v-else class="text-center text-gray-500 py-8">No team activity found</div>
<div
v-if="latestTeamActivity && latestTeamActivity.length <= 1"
class="text-center flex flex-1 justify-center items-center">
<div>
<UserGroupIcon
class="w-8 text-icon-default inline pb-2"></UserGroupIcon>
<h3 class="text-text-primary font-semibold text-sm">
Invite your co-workers
</h3>
<UserGroupIcon class="w-8 text-icon-default inline pb-2"></UserGroupIcon>
<h3 class="text-text-primary font-semibold text-sm">Invite your co-workers</h3>
<p class="pb-5 text-sm">You can invite your entire team.</p>
<SecondaryButton @click="router.visit(route('members'))"
>Go to Members

View File

@@ -13,20 +13,13 @@ defineProps<{
<p class="font-semibold text-sm text-text-primary">
{{ name }}
</p>
<div
v-if="working"
class="flex space-x-1.5 items-center justify-end">
<span
class="relative flex h-3 w-3 justify-center items-center">
<div v-if="working" class="flex space-x-1.5 items-center justify-end">
<span class="relative flex h-3 w-3 justify-center items-center">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75"></span>
<span
class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
<span
class="text-green-500 font-medium text-sm block pb-0.5">
working
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
<span class="text-green-500 font-medium text-sm block pb-0.5"> working </span>
</div>
</div>
<div

View File

@@ -24,14 +24,7 @@ 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,
]);
use([CanvasRenderer, BarChart, TitleComponent, GridComponent, TooltipComponent, LegendComponent]);
provide(THEME_KEY, 'dark');
@@ -246,15 +239,8 @@ const option = computed(() => {
<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" />
<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
@@ -294,9 +280,7 @@ const option = computed(() => {
" />
<ProjectsChartCard
v-if="weeklyProjectOverview"
:weekly-project-overview="
weeklyProjectOverview
"></ProjectsChartCard>
:weekly-project-overview="weeklyProjectOverview"></ProjectsChartCard>
</div>
</div>
</template>