design fixes, improve component encapsulation

This commit is contained in:
Gregor Vostrak
2025-11-06 14:20:12 +01:00
parent 8ba04eca0c
commit 0648437478
11 changed files with 26 additions and 19 deletions

View File

@@ -158,6 +158,7 @@ async function discardCurrentTimeEntry() {
} }
const { tags } = storeToRefs(useTagsStore()); const { tags } = storeToRefs(useTagsStore());
const { timeEntries } = storeToRefs(useTimeEntriesStore());
</script> </script>
<template> <template>
@@ -196,6 +197,7 @@ const { tags } = storeToRefs(useTagsStore());
:tags :tags
:tasks :tasks
:projects :projects
:time-entries
:create-tag :create-tag
:is-active :is-active
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"

View File

@@ -26,6 +26,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build && vue-tsc --emitDeclarationOnly", "build": "vite build && vue-tsc --emitDeclarationOnly",
"watch": "vite build --watch",
"types": "vue-tsc ", "types": "vue-tsc ",
"preview": "vite preview" "preview": "vite preview"
}, },

View File

@@ -318,7 +318,9 @@ watch(showEditTimeEntryModal, (value) => {
:tags="tags as any" :tags="tags as any"
:projects="projects" :projects="projects"
:tasks="tasks" :tasks="tasks"
:clients="clients" /> :clients="clients"
:currency="currency"
:can-create-project="canCreateProject" />
<FullCalendar ref="calendarRef" class="fullcalendar" :options="calendarOptions"> <FullCalendar ref="calendarRef" class="fullcalendar" :options="calendarOptions">
<template #eventContent="arg"> <template #eventContent="arg">
<FullCalendarEventContent <FullCalendarEventContent

View File

@@ -102,7 +102,7 @@ function onSelectChange(checked: boolean) {
) )
" "
@update:checked="onSelectChange" /> @update:checked="onSelectChange" />
<div class="flex items-center min-w-0"> <div class="flex items-center min-w-0 space-x-1 lg:space-x-2">
<GroupedItemsCountButton :expanded="expanded" @click="expanded = !expanded"> <GroupedItemsCountButton :expanded="expanded" @click="expanded = !expanded">
{{ timeEntry?.timeEntries?.length }} {{ timeEntry?.timeEntries?.length }}
</GroupedItemsCountButton> </GroupedItemsCountButton>
@@ -125,7 +125,7 @@ function onSelectChange(checked: boolean) {
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown> @changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
</div> </div>
</div> </div>
<div class="flex items-center font-medium lg:space-x-2"> <div class="flex items-center font-medium space-x-1 lg:space-x-2">
<TimeEntryRowTagDropdown <TimeEntryRowTagDropdown
:create-tag :create-tag
:tags="tags" :tags="tags"
@@ -142,8 +142,8 @@ function onSelectChange(checked: boolean) {
twMerge( twMerge(
'text-text-secondary px-1 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary', 'text-text-secondary px-1 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary',
organization?.time_format === '12-hours' organization?.time_format === '12-hours'
? 'w-[170px]' ? 'w-[150px]'
: 'w-[120px]' : 'w-[90px]'
) )
" "
@click="expanded = !expanded"> @click="expanded = !expanded">
@@ -157,7 +157,7 @@ function onSelectChange(checked: boolean) {
</button> </button>
</div> </div>
<button <button
class="text-text-primary min-w-[90px] px-2.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary" class="text-text-primary !mr-2 min-w-[70px] px-1.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary"
@click="expanded = !expanded"> @click="expanded = !expanded">
{{ {{
formatHumanReadableDuration( formatHumanReadableDuration(

View File

@@ -15,8 +15,6 @@ import type {
Client, Client,
TimeEntry, TimeEntry,
} from '@/packages/api/src'; } from '@/packages/api/src';
import { getOrganizationCurrencyString } from '@/utils/money';
import { canCreateProjects } from '@/utils/permissions';
import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue'; import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue';
import { Badge } from '@/packages/ui/src'; import { Badge } from '@/packages/ui/src';
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue'; import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
@@ -44,6 +42,8 @@ const props = defineProps<{
projects: Project[]; projects: Project[];
tasks: Task[]; tasks: Task[];
clients: Client[]; clients: Client[];
currency: string;
canCreateProject: boolean;
}>(); }>();
const description = ref<HTMLInputElement | null>(null); const description = ref<HTMLInputElement | null>(null);
@@ -163,8 +163,8 @@ type BillableOption = {
:clients :clients
:create-project :create-project
:create-client :create-client
:can-create-project="canCreateProjects()" :can-create-project="canCreateProject"
:currency="getOrganizationCurrencyString()" :currency="currency"
size="xlarge" size="xlarge"
class="bg-input-background" class="bg-input-background"
:projects="projects" :projects="projects"

View File

@@ -35,11 +35,11 @@ const organization = inject<ComputedRef<Organization>>('organization');
data-testid="time_entry_range_selector" data-testid="time_entry_range_selector"
:class=" :class="
twMerge( twMerge(
'text-text-secondary px-2 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:text-text-primary focus-visible:ring-ring focus-visible:bg-tertiary', 'text-text-secondary px-1 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:text-text-primary focus-visible:ring-ring focus-visible:bg-tertiary',
showDate showDate
? 'text-xs py-1.5 font-semibold' ? 'text-xs py-1.5 font-semibold'
: 'text-sm py-1.5 font-medium', : 'text-sm py-1.5 font-medium',
organization?.time_format === '12-hours' ? 'w-[170px]' : 'w-[120px]', organization?.time_format === '12-hours' ? 'w-[150px]' : 'w-[90px]',
open && 'border-card-border bg-card-background' open && 'border-card-border bg-card-background'
) )
"> ">

View File

@@ -186,7 +186,9 @@ async function handleDeleteTimeEntry() {
:tags="tags" :tags="tags"
:projects="projects" :projects="projects"
:tasks="tasks" :tasks="tasks"
:clients="clients" /> :clients="clients"
:currency="currency"
:can-create-project="canCreateProject" />
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -77,7 +77,7 @@ function selectInput(event: Event) {
v-model="currentTime" v-model="currentTime"
data-testid="time_entry_duration_input" data-testid="time_entry_duration_input"
name="Duration" name="Duration"
class="text-text-primary w-[90px] px-2.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring" class="text-text-primary w-[70px] !mr-2 px-1.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring"
@focus="selectInput" @focus="selectInput"
@keydown.tab="open = false" @keydown.tab="open = false"
@blur="updateTimerAndStartLiveTimerUpdate" @blur="updateTimerAndStartLiveTimerUpdate"

View File

@@ -61,7 +61,7 @@ function selectUnselectAll(value: boolean) {
{{ formatDate(date, organization?.date_format) }} {{ formatDate(date, organization?.date_format) }}
</span> </span>
</div> </div>
<div class="text-text-secondary pr-[90px] lg:pr-[92px]"> <div class="text-text-secondary pr-[87px] lg:pr-[92px]">
<span class="font-medium"> <span class="font-medium">
{{ {{
formatHumanReadableDuration( formatHumanReadableDuration(

View File

@@ -15,8 +15,6 @@ import type {
} from '@/packages/api/src'; } from '@/packages/api/src';
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
import { storeToRefs } from 'pinia';
import { useFocus } from '@vueuse/core'; import { useFocus } from '@vueuse/core';
import { autoUpdate, flip, limitShift, offset, shift, useFloating } from '@floating-ui/vue'; import { autoUpdate, flip, limitShift, offset, shift, useFloating } from '@floating-ui/vue';
import TimeTrackerRecentlyTrackedEntry from '@/packages/ui/src/TimeTracker/TimeTrackerRecentlyTrackedEntry.vue'; import TimeTrackerRecentlyTrackedEntry from '@/packages/ui/src/TimeTracker/TimeTrackerRecentlyTrackedEntry.vue';
@@ -34,6 +32,7 @@ const props = defineProps<{
tasks: Task[]; tasks: Task[];
tags: Tag[]; tags: Tag[];
clients: Client[]; clients: Client[];
timeEntries: TimeEntry[];
createTag: (name: string) => Promise<Tag | undefined>; createTag: (name: string) => Promise<Tag | undefined>;
createProject: (project: CreateProjectBody) => Promise<Project | undefined>; createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
createClient: (client: CreateClientBody) => Promise<Client | undefined>; createClient: (client: CreateClientBody) => Promise<Client | undefined>;
@@ -131,10 +130,9 @@ function updateTimeEntryDescription() {
} }
} }
const { timeEntries } = storeToRefs(useTimeEntriesStore());
const filteredRecentlyTrackedTimeEntries = computed(() => { const filteredRecentlyTrackedTimeEntries = computed(() => {
// do not include running time entries // do not include running time entries
const finishedTimeEntries = timeEntries.value.filter((item) => item.end !== null); const finishedTimeEntries = props.timeEntries.filter((item) => item.end !== null);
// filter out duplicates based on description, task, project, tags and billable // filter out duplicates based on description, task, project, tags and billable
const nonDuplicateTimeEntries = finishedTimeEntries.filter((item, index, self) => { const nonDuplicateTimeEntries = finishedTimeEntries.filter((item, index, self) => {

View File

@@ -20,6 +20,7 @@ import TextInput from './Input/TextInput.vue';
import InputLabel from './Input/InputLabel.vue'; import InputLabel from './Input/InputLabel.vue';
import TimeTrackerRunningInDifferentOrganizationOverlay from './TimeTracker/TimeTrackerRunningInDifferentOrganizationOverlay.vue'; import TimeTrackerRunningInDifferentOrganizationOverlay from './TimeTracker/TimeTrackerRunningInDifferentOrganizationOverlay.vue';
import TimeTrackerControls from './TimeTracker/TimeTrackerControls.vue'; import TimeTrackerControls from './TimeTracker/TimeTrackerControls.vue';
import TimeTrackerMoreOptionsDropdown from './TimeTracker/TimeTrackerMoreOptionsDropdown.vue';
import CardTitle from './CardTitle.vue'; import CardTitle from './CardTitle.vue';
import SelectDropdown from './Input/SelectDropdown.vue'; import SelectDropdown from './Input/SelectDropdown.vue';
import Badge from './Badge.vue'; import Badge from './Badge.vue';
@@ -48,6 +49,7 @@ export {
InputLabel, InputLabel,
TimeTrackerRunningInDifferentOrganizationOverlay, TimeTrackerRunningInDifferentOrganizationOverlay,
TimeTrackerControls, TimeTrackerControls,
TimeTrackerMoreOptionsDropdown,
CardTitle, CardTitle,
SelectDropdown, SelectDropdown,
Badge, Badge,