Skip to content

Commit 77a5e97

Browse files
authored
Added the ability to disable group similar time entries (#1054)
* Added the ability to disable group similar time entries * Fix E2E test for Group similar time entries * Simplify `TimeEntryGroupedTable` by replacing ternary with early return logic * Refactor time entry grouping settings: rename storage key, move logic into a dedicated module * Replace fixed `waitForTimeout` calls in E2E tests with element-based waits and assertions * Run frontend linting and formatting for changes
1 parent 353a579 commit 77a5e97

7 files changed

Lines changed: 98 additions & 0 deletions

File tree

e2e/profile.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,37 @@ test('test that theme can be changed to dark and light', async ({ page }) => {
230230
await expect(page.getByText('System default:')).toBeVisible();
231231
});
232232

233+
// =============================================
234+
// Group similar time entries
235+
// =============================================
236+
237+
test('test that group similar time entries setting can be toggled', async ({ page }) => {
238+
await goToProfilePage(page);
239+
240+
// Get the checkbox
241+
const checkbox = page.getByLabel('Group similar time entries');
242+
243+
// Get initial value and verify it is checked (default is true)
244+
const initialValue = await checkbox.isChecked();
245+
await expect(checkbox).toBeChecked();
246+
247+
// Toggle the checkbox
248+
await checkbox.click();
249+
250+
// Reload
251+
await page.reload();
252+
253+
// Verify the value is toggled
254+
const afterValue = await page.getByLabel('Group similar time entries').isChecked();
255+
expect(afterValue).toBe(!initialValue);
256+
257+
// Verify localStorage persists the setting
258+
const storedValue = await page.evaluate(() =>
259+
localStorage.getItem('group-similar-time-entries')
260+
);
261+
expect(storedValue).toBe(String(!initialValue));
262+
});
263+
233264
// =============================================
234265
// Two Factor Authentication Tests
235266
// =============================================

e2e/time.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ function getMonthFromTimestamp(timestamp: string): number {
3939
return new Date(timestamp).getUTCMonth() + 1;
4040
}
4141

42+
async function goToProfilePage(page: Page) {
43+
await page.goto(PLAYWRIGHT_BASE_URL + '/user/profile');
44+
}
45+
4246
async function goToTimeOverview(page: Page) {
4347
await page.goto(PLAYWRIGHT_BASE_URL + '/time');
4448
}
@@ -67,6 +71,14 @@ async function createEmptyTimeEntry(page: Page) {
6771
]);
6872
}
6973

74+
async function setTimeEntriesGrouping(page: Page, enabled: boolean) {
75+
await goToProfilePage(page);
76+
const checkbox = page.getByLabel('Group similar time entries');
77+
const isChecked = await checkbox.isChecked();
78+
if (isChecked !== enabled) await checkbox.click();
79+
await goToTimeOverview(page);
80+
}
81+
7082
test('test that starting and stopping an empty time entry shows a new time entry in the overview', async ({
7183
page,
7284
}) => {
@@ -333,6 +345,30 @@ test.skip('test that load more works when the end of page is reached', async ({
333345
await expect(page.locator('body')).toHaveText(/All time entries are loaded!/);
334346
});
335347

348+
test('test that Group similar time entries option is affected', async ({ page }) => {
349+
// Enable grouping
350+
await setTimeEntriesGrouping(page, true);
351+
352+
// Create 2 similar time entries
353+
await createEmptyTimeEntry(page);
354+
await page.waitForSelector('[data-testid="time_entry_row"]', { timeout: 1000 });
355+
await createEmptyTimeEntry(page);
356+
357+
// Verify similar time entries are grouped
358+
await expect(page.getByTestId('grouped_items_count_button').first()).toBeVisible({
359+
timeout: 1000,
360+
});
361+
362+
// Disable grouping
363+
await setTimeEntriesGrouping(page, false);
364+
365+
// Verify similar time entries are not grouped
366+
await expect(page.locator('[data-testid="time_entry_row"]')).toHaveCount(2, { timeout: 1000 });
367+
await expect(page.locator('[data-testid="grouped_items_count_button"]')).toHaveCount(0, {
368+
timeout: 1000,
369+
});
370+
});
371+
336372
// TODO: Test that updating the time entry start / end times works while it is running
337373

338374
// TODO: Test for project update

resources/js/Pages/Profile/Partials/ThemeForm.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
import FormSection from '@/Components/FormSection.vue';
33
import { Field, FieldLabel, FieldDescription } from '@/packages/ui/src/field';
44
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/packages/ui/src';
5+
import { Checkbox } from '@/packages/ui/src';
56
import { usePreferredColorScheme } from '@vueuse/core';
67
import { themeSetting } from '@/utils/theme';
8+
import { groupSimilarTimeEntriesSetting } from '@/utils/timeEntryGrouping';
79
810
const preferredColor = usePreferredColorScheme();
911
</script>
@@ -15,6 +17,7 @@ const preferredColor = usePreferredColorScheme();
1517
<template #description> Choose how you want solidtime to look on your device </template>
1618

1719
<template #form>
20+
<!-- Theme -->
1821
<Field class="col-span-6 sm:col-span-4">
1922
<FieldLabel for="theme">Theme</FieldLabel>
2023
<Select id="theme" v-model="themeSetting">
@@ -31,6 +34,14 @@ const preferredColor = usePreferredColorScheme();
3134
System default: {{ preferredColor }}
3235
</FieldDescription>
3336
</Field>
37+
38+
<!-- Group similar time entries -->
39+
<Field class="col-span-6 sm:col-span-4" orientation="horizontal">
40+
<Checkbox
41+
id="group_similar_time_entries"
42+
v-model:checked="groupSimilarTimeEntriesSetting" />
43+
<FieldLabel for="group_similar_time_entries">Group similar time entries</FieldLabel>
44+
</Field>
3445
</template>
3546
</FormSection>
3647
</template>

resources/js/Pages/Time.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useElementVisibility } from '@vueuse/core';
1616
import { ClockIcon } from '@heroicons/vue/20/solid';
1717
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
1818
import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
19+
import { groupSimilarTimeEntriesSetting } from '@/utils/timeEntryGrouping';
1920
import { useTasksQuery } from '@/utils/useTasksQuery';
2021
import { useProjectsQuery } from '@/utils/useProjectsQuery';
2122
import TimeEntryGroupedTable from '@/packages/ui/src/TimeEntry/TimeEntryGroupedTable.vue';
@@ -151,6 +152,7 @@ function deleteSelected() {
151152
:tasks="tasks"
152153
:currency="getOrganizationCurrencyString()"
153154
:time-entries="timeEntries"
155+
:group-similar-time-entries="groupSimilarTimeEntriesSetting"
154156
:tags="tags"></TimeEntryGroupedTable>
155157
<div v-if="isPending" class="flex justify-center items-center py-12">
156158
<LoadingSpinner></LoadingSpinner>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ const props = withDefaults(
66
defineProps<{
77
expanded?: boolean;
88
size?: string;
9+
/**
10+
* Test ID used for Playwright/E2E tests.
11+
*/
12+
testId?: string;
913
}>(),
1014
{
1115
expanded: false,
1216
size: 'w-7 h-7',
17+
testId: 'grouped_items_count_button',
1318
}
1419
);
1520
@@ -23,6 +28,7 @@ const expandedStatusClasses = computed(() => {
2328

2429
<template>
2530
<button
31+
:data-testid="props.testId"
2632
:class="
2733
twMerge(
2834
'font-medium text-base rounded flex items-center transition justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-transparent',

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const props = defineProps<{
3737
organizationBillableRate: number | null;
3838
enableEstimatedTime: boolean;
3939
canCreateProject: boolean;
40+
groupSimilarTimeEntries: boolean;
4041
}>();
4142
4243
const groupedTimeEntries = computed(() => {
@@ -58,6 +59,11 @@ const groupedTimeEntries = computed(() => {
5859
const newDailyEntries: TimeEntriesGroupedByType[] = [];
5960
6061
for (const entry of dailyEntries) {
62+
if (!props.groupSimilarTimeEntries) {
63+
newDailyEntries.push({ ...entry, timeEntries: [entry] });
64+
continue;
65+
}
66+
6167
// check if same entry already exists
6268
const oldEntriesIndex = newDailyEntries.findIndex(
6369
(e) =>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { useStorage } from '@vueuse/core';
2+
3+
export const groupSimilarTimeEntriesSetting = useStorage<boolean>(
4+
'group-similar-time-entries',
5+
true
6+
);

0 commit comments

Comments
 (0)