Compare commits

..

5 Commits

Author SHA1 Message Date
Gregor Vostrak
b660486eb7 fix e2e tests 2026-05-11 19:00:37 +02:00
Gregor Vostrak
61db23fc01 fix formatting 2026-05-11 17:28:29 +02:00
Gregor Vostrak
ab761d97f9 clamp running time entry duration to min 0 for FullCalendarHeaderDuration calc 2026-05-11 17:20:28 +02:00
Gregor Vostrak
50d60393bc fix formatting 2026-05-11 17:18:57 +02:00
Gregor Vostrak
b5639e4bb2 add missing dayjs plugins for isSameOrBefore and isSameOrAfter 2026-05-11 17:08:15 +02:00
10 changed files with 22 additions and 24 deletions

View File

@@ -907,7 +907,7 @@ test.describe('Employee Sidebar Navigation', () => {
// Visible links // Visible links
await expect(employee.page.getByRole('link', { name: 'Dashboard' })).toBeVisible(); await expect(employee.page.getByRole('link', { name: 'Dashboard' })).toBeVisible();
await expect(employee.page.getByRole('link', { name: 'Time' })).toBeVisible(); await expect(employee.page.getByRole('link', { name: 'Time', exact: true })).toBeVisible();
await expect(employee.page.getByRole('link', { name: 'Calendar' })).toBeVisible(); await expect(employee.page.getByRole('link', { name: 'Calendar' })).toBeVisible();
await expect(employee.page.getByRole('link', { name: 'Projects' })).toBeVisible(); await expect(employee.page.getByRole('link', { name: 'Projects' })).toBeVisible();
await expect(employee.page.getByRole('link', { name: 'Clients' })).toBeVisible(); await expect(employee.page.getByRole('link', { name: 'Clients' })).toBeVisible();

View File

@@ -640,7 +640,7 @@ test('test that creating a project with estimated time in human-readable format
await page.getByLabel('Project Name').fill(newProjectName); await page.getByLabel('Project Name').fill(newProjectName);
// Fill in estimated time using human-readable format // Fill in estimated time using human-readable format
const estimatedTimeInput = page.getByPlaceholder('e.g. 2h 30m or 1.5'); const estimatedTimeInput = page.getByLabel('Time Estimated');
await estimatedTimeInput.fill('2h 30m'); await estimatedTimeInput.fill('2h 30m');
await estimatedTimeInput.press('Tab'); await estimatedTimeInput.press('Tab');
@@ -668,7 +668,7 @@ test('test that creating a project with estimated time using decimal notation wo
await page.getByLabel('Project Name').fill(newProjectName); await page.getByLabel('Project Name').fill(newProjectName);
// Fill in estimated time using decimal notation (1.5 hours = 1h 30m) // Fill in estimated time using decimal notation (1.5 hours = 1h 30m)
const estimatedTimeInput = page.getByPlaceholder('e.g. 2h 30m or 1.5'); const estimatedTimeInput = page.getByLabel('Time Estimated');
await estimatedTimeInput.fill('1.5'); await estimatedTimeInput.fill('1.5');
await estimatedTimeInput.press('Tab'); await estimatedTimeInput.press('Tab');
@@ -696,7 +696,7 @@ test('test that creating a project with estimated time using comma decimal notat
await page.getByLabel('Project Name').fill(newProjectName); await page.getByLabel('Project Name').fill(newProjectName);
// Fill in estimated time using comma decimal notation (2,5 hours = 2h 30m) // Fill in estimated time using comma decimal notation (2,5 hours = 2h 30m)
const estimatedTimeInput = page.getByPlaceholder('e.g. 2h 30m or 1.5'); const estimatedTimeInput = page.getByLabel('Time Estimated');
await estimatedTimeInput.fill('2,5'); await estimatedTimeInput.fill('2,5');
await estimatedTimeInput.press('Tab'); await estimatedTimeInput.press('Tab');
@@ -727,7 +727,7 @@ test('test that updating estimated time on existing project works', async ({ pag
await page.getByRole('menuitem').getByText('Edit').first().click(); await page.getByRole('menuitem').getByText('Edit').first().click();
// Fill in estimated time // Fill in estimated time
const estimatedTimeInput = page.getByPlaceholder('e.g. 2h 30m or 1.5'); const estimatedTimeInput = page.getByLabel('Time Estimated');
await estimatedTimeInput.fill('4h 15m'); await estimatedTimeInput.fill('4h 15m');
await estimatedTimeInput.press('Tab'); await estimatedTimeInput.press('Tab');
@@ -748,7 +748,7 @@ test('test that estimated time input displays formatted value after blur', async
await goToProjectsOverview(page); await goToProjectsOverview(page);
await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByRole('button', { name: 'Create Project' }).click();
const estimatedTimeInput = page.getByPlaceholder('e.g. 2h 30m or 1.5'); const estimatedTimeInput = page.getByLabel('Time Estimated');
// Enter time in various formats and check the displayed value // Enter time in various formats and check the displayed value
await estimatedTimeInput.fill('90'); await estimatedTimeInput.fill('90');

View File

@@ -43,9 +43,7 @@ const emit = defineEmits<{
disabled:opacity-50 disabled:cursor-not-allowed" /> disabled:opacity-50 disabled:cursor-not-allowed" />
</span> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent> Stop the running time entry to edit the timesheet </TooltipContent>
Stop the running time entry to edit the timesheet
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
<DurationSecondsInput <DurationSecondsInput

View File

@@ -26,9 +26,7 @@ defineEmits<{
<Button variant="ghost" size="sm" :disabled="busy"> <Button variant="ghost" size="sm" :disabled="busy">
<LoadingSpinner v-if="busy" class="h-3.5 w-3.5 m-0" /> <LoadingSpinner v-if="busy" class="h-3.5 w-3.5 m-0" />
Copy last week Copy last week
<ChevronDownIcon <ChevronDownIcon v-if="!busy" class="h-3.5 w-3.5 ml-1 text-icon-default" />
v-if="!busy"
class="h-3.5 w-3.5 ml-1 text-icon-default" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" class="min-w-[220px]"> <DropdownMenuContent align="start" class="min-w-[220px]">

View File

@@ -58,7 +58,11 @@ const emit = defineEmits<{
<div class="inline-block min-w-full align-middle"> <div class="inline-block min-w-full align-middle">
<div <div
class="grid min-w-full w-max border-y border-default-background-separator" class="grid min-w-full w-max border-y border-default-background-separator"
style="grid-template-columns: minmax(420px, 1fr) repeat(7, minmax(96px, 120px)) minmax(100px, auto) 40px;"> style="
grid-template-columns:
minmax(420px, 1fr) repeat(7, minmax(96px, 120px)) minmax(100px, auto)
40px;
">
<!-- Header row --> <!-- Header row -->
<div <div
class="bg-background dark:bg-secondary pl-7 pr-3 py-1 text-xs text-text-tertiary md:sticky md:left-0 md:z-10"> class="bg-background dark:bg-secondary pl-7 pr-3 py-1 text-xs text-text-tertiary md:sticky md:left-0 md:z-10">

View File

@@ -170,12 +170,9 @@ async function createTag(name: string): Promise<Tag | undefined> {
@remove-row="handleRemoveRow" @remove-row="handleRemoveRow"
@cell-update="handleCellUpdate" @cell-update="handleCellUpdate"
@project-task-change=" @project-task-change="
(row, projectId, taskId) => (row, projectId, taskId) => handleRowIdentityChange(row, { projectId, taskId })
handleRowIdentityChange(row, { projectId, taskId })
"
@billable-change="
(row, billable) => handleRowIdentityChange(row, { billable })
" "
@billable-change="(row, billable) => handleRowIdentityChange(row, { billable })"
@tags-change="(row, tags) => handleRowIdentityChange(row, { tags })" @tags-change="(row, tags) => handleRowIdentityChange(row, { tags })"
@add-row="handleAddRow" /> @add-row="handleAddRow" />

View File

@@ -265,9 +265,9 @@ export function useCalendarEvents(params: {
'seconds' 'seconds'
); );
} else { } else {
durationSeconds = params.currentTime.value.diff( durationSeconds = Math.max(
getDayJsInstance()(entry.start), 0,
'seconds' params.currentTime.value.diff(getDayJsInstance()(entry.start), 'seconds')
); );
} }

View File

@@ -3,6 +3,8 @@ import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import isToday from 'dayjs/plugin/isToday'; import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday'; import isYesterday from 'dayjs/plugin/isYesterday';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import weekOfYear from 'dayjs/plugin/weekOfYear'; import weekOfYear from 'dayjs/plugin/weekOfYear';
@@ -68,6 +70,8 @@ function configureParseLocale(numberFormat?: string) {
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
dayjs.extend(isToday); dayjs.extend(isToday);
dayjs.extend(isYesterday); dayjs.extend(isYesterday);
dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);
dayjs.extend(duration); dayjs.extend(duration);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);

View File

@@ -88,7 +88,6 @@ export function findFreeWindowOnDay(
// Sort + merge so we can walk a clean [gap, obstacle, gap, ...] sequence. // Sort + merge so we can walk a clean [gap, obstacle, gap, ...] sequence.
obstacles.sort((a, b) => a.start.diff(b.start)); obstacles.sort((a, b) => a.start.diff(b.start));
// merge overlaps // merge overlaps
const merged: Interval[] = []; const merged: Interval[] = [];
for (const obs of obstacles) { for (const obs of obstacles) {

View File

@@ -19,7 +19,6 @@ import {
type FreeWindow, type FreeWindow,
} from './cellMath'; } from './cellMath';
/** /**
* Cell-level edit dispatcher. Picks one of four strategies based on * Cell-level edit dispatcher. Picks one of four strategies based on
* the diff between current and requested totals: * the diff between current and requested totals:
@@ -317,6 +316,5 @@ export function useTimesheetCellMutations(
return best; return best;
} }
return { handleCellUpdate }; return { handleCellUpdate };
} }