Skip to content

Commit 9be97a8

Browse files
committed
Improve Time page responsiveness and compact tags, fixes #896
1 parent 03e0377 commit 9be97a8

14 files changed

+188
-33
lines changed

resources/js/Pages/Time.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ function deleteSelected() {
114114
:tags="tags"
115115
:currency="getOrganizationCurrencyString()"
116116
:clients="clients"
117-
class="border-t border-default-background-separator"
117+
class="border-t border-default-background-separator hidden sm:block"
118118
:update-time-entries="
119119
(args) =>
120120
updateTimeEntries(

resources/js/packages/ui/src/Badge.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const tagClasses = computed(() => {
4747
tagClasses,
4848
badgeClasses[size],
4949
borderClasses,
50-
'rounded transition inline-flex items-center font-medium text-text-primary disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
50+
'rounded transition inline-flex items-center font-medium text-text-primary disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring min-w-0 overflow-hidden',
5151
props.class
5252
)
5353
">

resources/js/packages/ui/src/MainContainer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts"></script>
22

33
<template>
4-
<div class="px-3 sm:px-4 lg:px-6 mx-auto">
4+
<div class="px-2 sm:px-4 lg:px-6 mx-auto">
55
<slot></slot>
66
</div>
77
</template>

resources/js/packages/ui/src/Tag/TagBadge.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ const props = withDefaults(
1111
class?: string;
1212
color?: string;
1313
border?: boolean;
14+
showIcon?: boolean;
1415
}>(),
1516
{
1617
size: 'base',
1718
tag: 'div',
1819
color: 'var(--theme-color-icon-default)',
1920
border: true,
21+
showIcon: true,
2022
}
2123
);
2224
@@ -28,7 +30,7 @@ const indicatorClasses = {
2830

2931
<template>
3032
<Badge :name :size :tag :class="props.class" :color :border>
31-
<TagIcon :class="twMerge(indicatorClasses[size])"></TagIcon>
33+
<TagIcon v-if="showIcon" :class="twMerge(indicatorClasses[size])"></TagIcon>
3234
<span v-if="name">
3335
{{ name }}
3436
</span>

resources/js/packages/ui/src/Tag/TagDropdown.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ const props = withDefaults(
2323
tags: Tag[];
2424
createTag: (name: string) => Promise<Tag | undefined>;
2525
align?: 'center' | 'end' | 'start';
26+
showNoTagOption?: boolean;
2627
}>(),
2728
{
2829
align: 'start',
30+
showNoTagOption: true,
2931
}
3032
);
3133
@@ -55,6 +57,7 @@ const filteredTags = computed(() => {
5557
});
5658
5759
const showNoTag = computed(() => {
60+
if (!props.showNoTagOption) return false;
5861
const search = searchValue.value.toLowerCase().trim();
5962
if (!search) return true;
6063
return NO_TAG_LABEL.toLowerCase().includes(search);
@@ -101,7 +104,7 @@ const showCreateTagModal = ref(false);
101104
<template #content>
102105
<ComboboxRoot
103106
v-model:search-term="searchValue"
104-
:open="open"
107+
:open="true"
105108
class="p-2"
106109
:filter-function="(val: string[]) => val">
107110
<ComboboxAnchor>

resources/js/packages/ui/src/TimeEntry/TimeEntryAggregateRow.vue

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ function onSelectChange(checked: boolean) {
9494
data-testid="time_entry_row">
9595
<MainContainer class="min-w-0">
9696
<div class="@xl:flex py-2 items-center min-w-0 justify-between group">
97-
<div class="flex space-x-3 items-center min-w-0">
97+
<!-- Desktop layout -->
98+
<div class="hidden @lg:flex space-x-3 items-center min-w-0">
9899
<Checkbox
99100
:checked="
100101
timeEntry.timeEntries.every((aggregateTimeEntry: TimeEntry) =>
@@ -107,10 +108,11 @@ function onSelectChange(checked: boolean) {
107108
{{ timeEntry?.timeEntries?.length }}
108109
</GroupedItemsCountButton>
109110
<TimeEntryDescriptionInput
110-
class="min-w-0 mr-4"
111+
class="min-w-0 mr-4 shrink"
111112
:model-value="timeEntry.description"
112113
@changed="updateTimeEntryDescription"></TimeEntryDescriptionInput>
113114
<TimeTrackerProjectTaskDropdown
115+
class="min-w-0 shrink"
114116
:clients
115117
:create-project
116118
:create-client
@@ -125,7 +127,8 @@ function onSelectChange(checked: boolean) {
125127
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
126128
</div>
127129
</div>
128-
<div class="flex items-center font-medium space-x-1 @lg:space-x-2">
130+
<div
131+
class="hidden @lg:flex items-center font-medium space-x-1 @lg:space-x-2 shrink-0">
129132
<TimeEntryRowTagDropdown
130133
:create-tag
131134
:tags="tags"
@@ -180,6 +183,74 @@ function onSelectChange(checked: boolean) {
180183
deleteTimeEntries(timeEntry?.timeEntries ?? [])
181184
"></TimeEntryMoreOptionsDropdown>
182185
</div>
186+
<!-- Mobile layout -->
187+
<div class="@lg:hidden">
188+
<!-- First row: count + description + duration -->
189+
<div class="flex items-center justify-between min-w-0">
190+
<div class="flex items-center min-w-0 flex-1">
191+
<GroupedItemsCountButton
192+
:expanded="expanded"
193+
@click="expanded = !expanded">
194+
{{ timeEntry?.timeEntries?.length }}
195+
</GroupedItemsCountButton>
196+
<TimeEntryDescriptionInput
197+
class="min-w-0 flex-1"
198+
:model-value="timeEntry.description"
199+
@changed="updateTimeEntryDescription"></TimeEntryDescriptionInput>
200+
</div>
201+
<button
202+
class="text-text-primary min-w-[80px] 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"
203+
@click="expanded = !expanded">
204+
{{
205+
formatHumanReadableDuration(
206+
timeEntry.duration ?? 0,
207+
organization?.interval_format,
208+
organization?.number_format
209+
)
210+
}}
211+
</button>
212+
</div>
213+
<!-- Second row: project/task - tags - billable - start - more -->
214+
<div class="flex items-center justify-between mt-1">
215+
<TimeTrackerProjectTaskDropdown
216+
class="min-w-0"
217+
:clients
218+
:create-project
219+
:create-client
220+
:can-create-project
221+
:projects="projects"
222+
:tasks="tasks"
223+
:show-badge-border="false"
224+
:project="timeEntry.project_id"
225+
:enable-estimated-time
226+
:currency="currency"
227+
:task="timeEntry.task_id"
228+
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
229+
<div class="flex items-center shrink-0">
230+
<TimeEntryRowTagDropdown
231+
:create-tag
232+
:tags="tags"
233+
:model-value="timeEntry.tags"
234+
compact
235+
@changed="updateTimeEntryTags"></TimeEntryRowTagDropdown>
236+
<BillableToggleButton
237+
:model-value="timeEntry.billable"
238+
size="small"
239+
@changed="updateTimeEntryBillable"></BillableToggleButton>
240+
<TimeTrackerStartStop
241+
:active="!!(timeEntry.start && !timeEntry.end)"
242+
variant="secondary"
243+
class="ml-2"
244+
@changed="onStartStopClick(timeEntry)"></TimeTrackerStartStop>
245+
<TimeEntryMoreOptionsDropdown
246+
:show-edit="false"
247+
:show-duplicate="false"
248+
@delete="
249+
deleteTimeEntries(timeEntry?.timeEntries ?? [])
250+
"></TimeEntryMoreOptionsDropdown>
251+
</div>
252+
</div>
253+
</div>
183254
</div>
184255
</MainContainer>
185256
<div

resources/js/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ const displaysPlaceholder = computed(() => {
3131
</script>
3232

3333
<template>
34-
<div class="relative min-w-0 flex-1 text-ellipsis whitespace-nowrap overflow-hidden">
34+
<div class="relative min-w-0 text-ellipsis whitespace-nowrap overflow-hidden">
3535
<div class="relative text-sm font-medium min-w-0">
3636
<div
3737
:class="[
38-
'opacity-0 h-4 text-sm whitespace-pre font-medium min-w-0 pl-3 pr-1',
38+
'opacity-0 h-4 text-sm whitespace-pre font-medium min-w-0 pl-1.5 @lg:pl-3 pr-1',
3939
{ 'min-w-[130px]': displaysPlaceholder },
4040
]">
4141
{{ liveDataValue }}
@@ -44,7 +44,7 @@ const displaysPlaceholder = computed(() => {
4444
data-testid="time_entry_description"
4545
:value="liveDataValue"
4646
placeholder="Add a description"
47-
class="absolute px-0 h-full min-w-0 pl-3 pr-1 left-0 top-0 w-full text-sm text-text-primary font-medium bg-transparent focus-visible:ring-0 rounded-lg border-0"
47+
class="absolute px-0 h-full min-w-0 pl-1.5 @lg:pl-3 pr-1 left-0 top-0 w-full text-sm text-text-primary font-medium bg-transparent focus-visible:ring-0 rounded-lg border-0"
4848
@blur="onChange"
4949
@input="onInput"
5050
@keydown.enter="onChange" />

resources/js/packages/ui/src/TimeEntry/TimeEntryMassActionRow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const showMassUpdateModal = ref(false);
6363
:class="
6464
twMerge(
6565
props.class,
66-
'text-sm py-1.5 font-medium flex border-b border-border-primary items-center space-x-3'
66+
'text-sm py-1.5 font-medium hidden sm:flex border-b border-border-primary items-center space-x-3'
6767
)
6868
">
6969
<Checkbox

resources/js/packages/ui/src/TimeEntry/TimeEntryRow.vue

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,16 @@ async function handleDeleteTimeEntry() {
113113
data-testid="time_entry_row">
114114
<MainContainer class="min-w-0">
115115
<div class="@xl:flex py-2 min-w-0 items-center justify-between group">
116-
<div class="flex items-center min-w-0">
116+
<!-- Desktop layout -->
117+
<div class="hidden @lg:flex items-center min-w-0">
117118
<Checkbox :checked="selected" @update:checked="onSelectChange" />
118119
<div v-if="indent === true" class="w-10 h-7"></div>
119120
<TimeEntryDescriptionInput
120-
class="min-w-0 mr-4"
121+
class="min-w-0 mr-4 shrink"
121122
:model-value="timeEntry.description"
122123
@changed="updateTimeEntryDescription"></TimeEntryDescriptionInput>
123124
<TimeTrackerProjectTaskDropdown
125+
class="min-w-0 shrink"
124126
:create-project
125127
:create-client
126128
:can-create-project
@@ -134,7 +136,8 @@ async function handleDeleteTimeEntry() {
134136
:task="timeEntry.task_id"
135137
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
136138
</div>
137-
<div class="flex items-center font-medium space-x-1 @lg:space-x-2">
139+
<div
140+
class="hidden @lg:flex items-center font-medium space-x-1 @lg:space-x-2 shrink-0">
138141
<div v-if="showMember && members" class="text-sm px-2">
139142
{{ memberName }}
140143
</div>
@@ -171,6 +174,58 @@ async function handleDeleteTimeEntry() {
171174
@duplicate="duplicateTimeEntry"
172175
@delete="deleteTimeEntry"></TimeEntryMoreOptionsDropdown>
173176
</div>
177+
<!-- Mobile layout -->
178+
<div class="@lg:hidden">
179+
<!-- First row: description + duration -->
180+
<div class="flex items-center justify-between min-w-0">
181+
<TimeEntryDescriptionInput
182+
class="min-w-0 flex-1"
183+
:model-value="timeEntry.description"
184+
@changed="updateTimeEntryDescription"></TimeEntryDescriptionInput>
185+
<TimeEntryRowDurationInput
186+
:start="timeEntry.start"
187+
:end="timeEntry.end"
188+
@changed="updateStartEndTime"></TimeEntryRowDurationInput>
189+
</div>
190+
<!-- Second row: project/task - tags - billable - start - more -->
191+
<div class="flex items-center justify-between mt-1">
192+
<TimeTrackerProjectTaskDropdown
193+
class="min-w-0"
194+
:create-project
195+
:create-client
196+
:can-create-project
197+
:clients
198+
:projects="projects"
199+
:tasks="tasks"
200+
:show-badge-border="false"
201+
:project="timeEntry.project_id"
202+
:currency="currency"
203+
:enable-estimated-time
204+
:task="timeEntry.task_id"
205+
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
206+
<div class="flex items-center shrink-0">
207+
<TimeEntryRowTagDropdown
208+
:create-tag
209+
:tags="tags"
210+
:model-value="timeEntry.tags"
211+
compact
212+
@changed="updateTimeEntryTags"></TimeEntryRowTagDropdown>
213+
<BillableToggleButton
214+
:model-value="timeEntry.billable"
215+
size="small"
216+
@changed="updateTimeEntryBillable"></BillableToggleButton>
217+
<TimeTrackerStartStop
218+
:active="!!(timeEntry.start && !timeEntry.end)"
219+
variant="secondary"
220+
class="ml-2"
221+
@changed="onStartStopClick"></TimeTrackerStartStop>
222+
<TimeEntryMoreOptionsDropdown
223+
@edit="handleEdit"
224+
@duplicate="duplicateTimeEntry"
225+
@delete="deleteTimeEntry"></TimeEntryMoreOptionsDropdown>
226+
</div>
227+
</div>
228+
</div>
174229
</div>
175230
</MainContainer>
176231
</div>

resources/js/packages/ui/src/TimeEntry/TimeEntryRowHeading.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ function selectUnselectAll(value: boolean) {
3636
class="bg-background dark:bg-secondary border-b border-border-primary py-1 text-xs @sm:text-sm">
3737
<MainContainer>
3838
<div class="flex group justify-between items-center">
39-
<div class="flex items-center space-x-2">
40-
<div class="w-5">
39+
<div class="flex items-center @lg:space-x-2 pl-1.5 @lg:pl-0">
40+
<div class="w-5 hidden @lg:block">
4141
<CalendarIcon
4242
class="w-3 @sm:w-4 text-icon-default group-hover:hidden block">
4343
</CalendarIcon>
@@ -50,11 +50,11 @@ function selectUnselectAll(value: boolean) {
5050
<span class="font-medium text-text-secondary">
5151
{{ formatWeekday(date) }}
5252
</span>
53-
<span class="text-text-tertiary">
53+
<span class="text-text-tertiary ml-2">
5454
{{ formatDate(date, organization?.date_format) }}
5555
</span>
5656
</div>
57-
<div class="text-text-secondary pr-[87px] @lg:pr-[92px]">
57+
<div class="text-text-secondary pr-2 @lg:pr-[92px]">
5858
<span class="font-medium">
5959
{{
6060
formatHumanReadableDuration(

0 commit comments

Comments
 (0)