add break time entries and simplified time tracker ui

This commit is contained in:
Gregor Vostrak
2026-07-21 16:48:37 +02:00
parent c07c62bfab
commit 64ca1e9115
128 changed files with 6252 additions and 437 deletions

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { useBreaksEnabled } from '@/packages/ui/src/utils/useBreaksEnabled';
import { CheckCircleIcon, TagIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
import { FolderIcon } from '@heroicons/vue/16/solid';
import { Check } from '@lucide/vue';
import { Check, Coffee } from '@lucide/vue';
import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot, type AcceptableValue } from 'reka-ui';
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
import ReportingRoundingControls from '@/Components/Common/Reporting/ReportingRoundingControls.vue';
@@ -27,6 +28,7 @@ const selectedClients = defineModel<string[]>('selectedClients', { required: tru
const selectedTags = defineModel<string[]>('selectedTags', { required: true });
const tagMatchType = defineModel<TagMatchType>('tagMatchType', { required: true });
const billable = defineModel<'true' | 'false' | null>('billable', { required: true });
const entryType = defineModel<'work' | 'break' | null>('entryType', { required: true });
const roundingEnabled = defineModel<boolean>('roundingEnabled', { required: true });
const roundingType = defineModel<TimeEntryRoundingType>('roundingType', { required: true });
const roundingMinutes = defineModel<number>('roundingMinutes', { required: true });
@@ -37,6 +39,8 @@ const emit = defineEmits<{
submit: [];
}>();
const breaksEnabled = useBreaksEnabled();
const { tags } = useTagsQuery();
const tagMatchOptions: { value: TagMatchType; label: string }[] = [
@@ -162,6 +166,38 @@ async function createTag(name: string) {
<SelectItem value="false">Non Billable</SelectItem>
</SelectContent>
</Select>
<Select
v-if="breaksEnabled"
v-model="entryType"
@update:model-value="emit('submit')">
<SelectTrigger
size="sm"
variant="outline"
:active="entryType !== null"
:show-chevron="false">
<SelectValue class="flex items-center gap-2">
<Coffee
class="h-4 w-4"
:class="
entryType !== null
? 'dark:text-accent-300/80 text-accent-400/80'
: 'text-text-quaternary'
" />
<span class="text-text-secondary">{{
entryType === null
? 'Type'
: entryType === 'break'
? 'Breaks'
: 'Work time'
}}</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem :value="null">Both</SelectItem>
<SelectItem value="work">Work time</SelectItem>
<SelectItem value="break">Breaks</SelectItem>
</SelectContent>
</Select>
<ReportingRoundingControls
v-model:enabled="roundingEnabled"
v-model:type="roundingType"

View File

@@ -71,6 +71,7 @@ const selectedClients = ref<string[]>([]);
const tagMatchType = ref<TagMatchType>('contains');
const billable = ref<'true' | 'false' | null>(null);
const entryType = ref<'work' | 'break' | null>('work');
const roundingEnabled = ref<boolean>(false);
const roundingType = ref<TimeEntryRoundingType>('nearest');
const roundingMinutes = ref<number>(15);
@@ -126,6 +127,7 @@ const filterParams = computed<AggregatedTimeEntriesQueryParams>(() => {
tag_ids: selectedTags.value.length > 0 ? selectedTags.value : undefined,
tag_match_type: selectedTags.value.length > 0 ? tagMatchType.value : undefined,
billable: billable.value !== null ? billable.value : undefined,
type: entryType.value !== null ? entryType.value : undefined,
member_id: getCurrentRole() === 'employee' ? getCurrentMembershipId() : undefined,
rounding_type: roundingEnabled.value ? roundingType.value : undefined,
rounding_minutes: roundingEnabled.value ? roundingMinutes.value : undefined,
@@ -160,7 +162,7 @@ const aggregatedTableTimeEntries = computed<AggregatedTimeEntries | undefined>((
});
const reportProperties = computed(() => {
const { billable: billableFilter, ...rest } = filterParams.value;
const { billable: billableFilter, type: typeFilter, ...rest } = filterParams.value;
let billableValue: boolean | null = null;
if (billableFilter === 'true') {
@@ -172,6 +174,7 @@ const reportProperties = computed(() => {
return {
...rest,
billable: billableValue,
time_entry_type: typeFilter ?? null,
group: group.value,
sub_group: subGroup.value,
history_group: getOptimalGroupingOption(startDate.value, endDate.value),
@@ -371,6 +374,7 @@ const tableData = computed(() => {
v-model:selected-tags="selectedTags"
v-model:tag-match-type="tagMatchType"
v-model:billable="billable"
v-model:entry-type="entryType"
v-model:rounding-enabled="roundingEnabled"
v-model:rounding-type="roundingType"
v-model:rounding-minutes="roundingMinutes"