mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
design fixes, improve component encapsulation
This commit is contained in:
@@ -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()"
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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'
|
||||||
)
|
)
|
||||||
">
|
">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user