Enable npm workspaces and update dependencies

This commit is contained in:
Gregor Vostrak
2026-02-02 03:19:22 +01:00
parent b2af9c6bf1
commit 7efb7e6071
31 changed files with 2502 additions and 7953 deletions

View File

@@ -187,10 +187,10 @@ test('test that entering a random value in the time range does not start the tim
}) => {
await goToDashboard(page);
await page.getByTestId('time_entry_time').fill('asdasdasd');
await page.getByTestId('time_entry_time').press('Tab'),
(await page.getByTestId('time_entry_time').press('Tab'),
await page.locator(
'[data-testid="dashboard_timer"] [data-testid="timer_button"].bg-accent-300/70'
);
));
});
test('test that entering a time starts the timer on enter', async ({ page }) => {

4134
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,10 @@
{
"private": true,
"type": "module",
"workspaces": [
"resources/js/packages/ui",
"resources/js/packages/api"
],
"scripts": {
"dev": "vite",
"build": "vite build",
@@ -19,27 +23,29 @@
"@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/chroma-js": "2.4.5",
"@types/chroma-js": "^3.1.0",
"@types/node": "^22.10.10",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.5.1",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.0",
"autoprefixer": "^10.4.20",
"axios": "^1.6.4",
"eslint-plugin-unused-imports": "^4.1.4",
"laravel-vite-plugin": "^1.0.0",
"laravel-vite-plugin": "^2.1.0",
"openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.13",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vite-plugin-checker": "^0.8.0",
"vite": "^7.0.0",
"vite-plugin-checker": "^0.12.0",
"vue": "^3.5.0",
"vue-tsc": "^2.2.0"
"vue-tsc": "^3.0.0"
},
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/vue": "^1.0.6",
"@zodios/core": "^10.9.6",
"@fullcalendar/core": "^6.1.18",
"@fullcalendar/daygrid": "^6.1.18",
"@fullcalendar/interaction": "^6.1.18",
@@ -54,22 +60,23 @@
"@tanstack/vue-table": "^8.21.2",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0",
"@vueuse/core": "^12.8.2",
"@vueuse/integrations": "^12.5.0",
"@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^14.0.0",
"chroma-js": "3.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"focus-trap": "^7.6.0",
"echarts": "^6.0.0",
"focus-trap": "^8.0.0",
"lucide-vue-next": "^0.487.0",
"parse-duration": "^2.0.1",
"pinia": "^2.1.7",
"pinia": "^3.0.0",
"radix-vue": "^1.9.6",
"reka-ui": "^2.7.0",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vue-echarts": "^7.0.3"
"vue-echarts": "^8.0.0",
"zod": "^3.23.8"
},
"overrides": {
"vite-plugin-checker": {

View File

@@ -125,7 +125,7 @@ const gridTemplate = computed(() => {
:create-client
:currency="getOrganizationCurrencyString()"
:clients="clients"
:enable-estimated-time="isAllowedToPerformPremiumAction"></ProjectCreateModal>
:enable-estimated-time="isAllowedToPerformPremiumAction()"></ProjectCreateModal>
<div class="flow-root max-w-[100vw] overflow-x-auto">
<div class="inline-block min-w-full align-middle">
<div data-testid="project_table" class="grid min-w-full" :style="gridTemplate">

View File

@@ -158,7 +158,7 @@ const option = computed(() => {
if (dailyHoursTracked?.value) {
return (
formatDate(
dailyHoursTracked?.value[dataIndex].date,
dailyHoursTracked?.value[dataIndex]?.date ?? '',
organization?.value?.date_format
) +
': ' +

View File

@@ -23,9 +23,9 @@ const { data: last7Days, isLoading } = useQuery({
},
enabled: computed(() => !!organizationId.value),
placeholderData: Array.from({ length: 7 }, (_, i) => ({
date: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
date: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split('T')[0]!,
duration: 0,
history: Array(8).fill(0),
history: Array(8).fill(0) as number[],
})),
});
</script>

View File

@@ -42,10 +42,10 @@ const weekdays = computed(() => {
};
if (dayMapping[getWeekStart()]) {
const customOrder = [];
const startIndex = daysOrder.indexOf(dayMapping[getWeekStart()]);
const startIndex = daysOrder.indexOf(dayMapping[getWeekStart()]!);
for (let i = startIndex; i < 7 + startIndex; i++) {
customOrder.push(daysOrder[i % daysOrder.length]);
customOrder.push(daysOrder[i % daysOrder.length]!);
}
return customOrder;

View File

@@ -29,7 +29,7 @@ const photoInput = ref<HTMLInputElement | null>(null);
const updateProfileInformation = () => {
if (photoInput.value && photoInput.value.files && photoInput.value.files?.length > 0) {
form.photo = photoInput.value?.files[0];
form.photo = photoInput.value?.files[0] ?? null;
}
form.post(route('user-profile-information.update'), {

View File

@@ -137,7 +137,7 @@ const showBillableRate = computed(() => {
<ProjectCreateModal
v-model:show="showCreateProjectModal"
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction"
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:create-client
:currency="getOrganizationCurrencyString()"
:clients="clients"

View File

@@ -37,7 +37,7 @@ onMounted(() => {
const currentUrl = window.location.href;
// check if # exists exactly once in the URL
if (currentUrl.split('#').length === 2) {
sharedSecret.value = currentUrl.split('#')[1];
sharedSecret.value = currentUrl.split('#')[1] ?? null;
}
});
@@ -124,9 +124,10 @@ const groupedPieChartData = computed(() => {
if (entry.description === null) {
return {
value: entry.seconds,
name: emptyPlaceholder[
aggregatedTableTimeEntries.value?.grouped_type ?? 'project'
],
name:
emptyPlaceholder[
aggregatedTableTimeEntries.value?.grouped_type ?? 'project'
] ?? '',
color: '#CCCCCC',
};
}
@@ -146,14 +147,17 @@ const tableData = computed(() => {
cost: entry.cost,
description:
entry.description ??
emptyPlaceholder[aggregatedTableTimeEntries.value?.grouped_type ?? 'project'],
emptyPlaceholder[aggregatedTableTimeEntries.value?.grouped_type ?? 'project'] ??
'',
grouped_data:
entry.grouped_data?.map((el) => {
return {
seconds: el.seconds,
cost: el.cost,
description:
el.description ?? emptyPlaceholder[entry.grouped_type ?? 'project'],
el.description ??
emptyPlaceholder[entry.grouped_type ?? 'project'] ??
'',
};
}) ?? [],
};

View File

@@ -46,8 +46,8 @@ async function importData() {
addNotification('error', 'Please select the CSV or ZIP file that you want to import');
return;
}
const rawBase64String = await toBase64(files.value[0]);
const base64String = rawBase64String.split(';')[1].replace('base64,', '') as string;
const rawBase64String = await toBase64(files.value[0]!);
const base64String = rawBase64String.split(';')[1]!.replace('base64,', '') as string;
const organizationId = getCurrentOrganizationId();
if (organizationId !== null) {
const { handleApiRequestNotifications } = useNotificationsStore();

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"directory": "resources/js/packages/api"
},
"scripts": {
"build": "npx vite build",
"build": "vite build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
@@ -27,14 +27,14 @@
"type": "module",
"author": "solidtime",
"license": "AGPL-3.0",
"dependencies": {
"@zodios/core": "^10.9.6",
"axios": "^1.13.2",
"typescript": "^5.5.4",
"zod": "^3.23.8"
},
"devDependencies": {
"vite": "^5.4.2",
"vite-plugin-dts": "^4.0.3"
},
"peerDependencies": {
"@zodios/core": "^10.9.6",
"axios": "^1.6.4",
"typescript": "^5.5.4",
"vite": "^5.4.1 || ^6.0.0 || ^7.0.0",
"zod": "^3.23.8"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -48,22 +48,13 @@
"author": "solidtime",
"license": "AGPL-3.0",
"devDependencies": {
"@types/node": "^22.4.1",
"@vitejs/plugin-vue": "^5.1.2",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.14",
"postcss-import": "^15.1.0",
"postcss-nesting": "^12.1.0",
"typescript": "^5.5.4",
"vite": "^5.4.1",
"vite-plugin-dts": "^4.0.3"
},
"peerDependencies": {
"@floating-ui/vue": "^1.1.4",
"@heroicons/vue": "^2.1.5",
"@vueuse/core": "^12.5.0",
"@zodios/core": "^10.9.6",
"@vitejs/plugin-vue": "^5.1.2 || ^6.0.0",
"@vueuse/core": "^12.5.0 || ^14.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
@@ -71,7 +62,9 @@
"reka-ui": "^2.2.0",
"tailwind-merge": "^2.5.2",
"tailwindcss": "^3.1.0",
"typescript": "^5.5.4",
"vite": "^5.4.1 || ^6.0.0 || ^7.0.0",
"vue": "^3.5.0",
"vue-tsc": "^2.2.0"
"vue-tsc": "^2.2.0 || ^3.0.0"
}
}

View File

@@ -320,8 +320,8 @@ function getSlotDuration(calendarEl: HTMLElement): number {
const secondTime = secondSlot.getAttribute('data-time');
if (firstTime && secondTime) {
const [h1, m1] = firstTime.split(':').map(Number);
const [h2, m2] = secondTime.split(':').map(Number);
const [h1 = 0, m1 = 0] = firstTime.split(':').map(Number);
const [h2 = 0, m2 = 0] = secondTime.split(':').map(Number);
const diff = h2 * 60 + m2 - (h1 * 60 + m1);
if (diff > 0) return diff;
}

View File

@@ -41,8 +41,8 @@ function updateDuration() {
const results = parseHHMM(temporaryCustomTimerEntry.value);
if (results) {
const newStartDate = getDayJsInstance()(end.value)
.subtract(parseInt(results[1]), 'h')
.subtract(parseInt(results[2]), 'm');
.subtract(parseInt(results[1]!), 'h')
.subtract(parseInt(results[2]!), 'm');
start.value = newStartDate.utc().format();
}
}

View File

@@ -25,7 +25,7 @@ function updateTime(event: Event) {
const target = event.target as HTMLInputElement;
const newValue = target.value.trim();
if (newValue.split(':').length === 2) {
const [hours, minutes] = newValue.split(':');
const [hours, minutes] = newValue.split(':') as [string, string];
if (!isNaN(parseInt(hours)) && !isNaN(parseInt(minutes))) {
const currentTime = getLocalizedDayJs(model.value);
const newHours = Math.min(parseInt(hours), 23);

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { defineProps, nextTick, ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time';
import dayjs from 'dayjs';

View File

@@ -53,7 +53,7 @@ const groupedTimeEntries = computed(() => {
}
const groupedEntriesByDayAndType: Record<string, TimeEntriesGroupedByType[]> = {};
for (const dailyEntriesKey in groupedEntriesByDay) {
const dailyEntries = groupedEntriesByDay[dailyEntriesKey];
const dailyEntries = groupedEntriesByDay[dailyEntriesKey]!;
const newDailyEntries: TimeEntriesGroupedByType[] = [];
for (const entry of dailyEntries) {
@@ -66,26 +66,22 @@ const groupedTimeEntries = computed(() => {
e.description === entry.description
);
if (oldEntriesIndex !== -1 && newDailyEntries[oldEntriesIndex]) {
newDailyEntries[oldEntriesIndex].timeEntries.push(entry);
const existingEntry = newDailyEntries[oldEntriesIndex]!;
existingEntry.timeEntries.push(entry);
// Add up durations for time entries of the same type
newDailyEntries[oldEntriesIndex].duration =
(newDailyEntries[oldEntriesIndex].duration ?? 0) + (entry?.duration ?? 0);
existingEntry.duration = (existingEntry.duration ?? 0) + (entry?.duration ?? 0);
// adapt start end times so they show the earliest start and latest end time
if (
getDayJsInstance()(entry.start).isBefore(
getDayJsInstance()(newDailyEntries[oldEntriesIndex].start)
getDayJsInstance()(existingEntry.start)
)
) {
newDailyEntries[oldEntriesIndex].start = entry.start;
existingEntry.start = entry.start;
}
if (
getDayJsInstance()(entry.end).isAfter(
getDayJsInstance()(newDailyEntries[oldEntriesIndex].end)
)
) {
newDailyEntries[oldEntriesIndex].end = entry.end;
if (getDayJsInstance()(entry.end).isAfter(getDayJsInstance()(existingEntry.end))) {
existingEntry.end = entry.end;
}
} else {
newDailyEntries.push({ ...entry, timeEntries: [entry] });
@@ -204,7 +200,7 @@ function unselectAllTimeEntries(value: TimeEntriesGroupedByType[]) {
:delete-time-entry="() => deleteTimeEntries([entry])"
:duplicate-time-entry="() => createTimeEntry(entry)"
:currency="currency"
:time-entry="entry.timeEntries[0]"
:time-entry="entry.timeEntries[0]!"
@selected="selectedTimeEntries.push(entry)"
@unselected="
selectedTimeEntries = selectedTimeEntries.filter(

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
import { defineProps, ref, inject, type ComputedRef } from 'vue';
import { ref, inject, type ComputedRef } from 'vue';
import { formatDateLocalized, formatStartEnd } from '@/packages/ui/src/utils/time';
import TimeRangeSelector from '@/packages/ui/src/Input/TimeRangeSelector.vue';
import { twMerge } from 'tailwind-merge';

View File

@@ -4,7 +4,7 @@ import {
formatHumanReadableDuration,
parseTimeInput,
} from '@/packages/ui/src/utils/time';
import { computed, defineProps, ref, inject, type ComputedRef } from 'vue';
import { computed, ref, inject, type ComputedRef } from 'vue';
import dayjs from 'dayjs';
import type { Organization } from '@/packages/api/src';

View File

@@ -92,7 +92,7 @@ function addProjectToFilterObject(
if (projectClientIndex !== -1) {
// client already exists in filter array
tempFilteredClients[projectClientIndex].projects.push({
tempFilteredClients[projectClientIndex]!.projects.push({
...project,
expanded: expanded,
tasks: filteredTasks,
@@ -119,7 +119,7 @@ function addProjectToFilterObject(
if (noClientIndex !== -1) {
// no client group already exists in filter array
tempFilteredClients[noClientIndex].projects.push({
tempFilteredClients[noClientIndex]!.projects.push({
...project,
expanded: expanded,
tasks: filteredTasks,
@@ -253,7 +253,7 @@ function isProjectSelected(project: Project) {
function initializeHighlightedItem() {
if (filteredProjects.value.length > 0) {
highlightedItemId.value = filteredProjects.value[0].id;
highlightedItemId.value = filteredProjects.value[0]!.id;
}
}
@@ -312,27 +312,27 @@ function moveHighlightUp() {
highlightedItemId.value = currentProjectWithTasks.id;
return;
}
highlightedItemId.value = currentProjectWithTasks.tasks[taskIndex - 1].id;
highlightedItemId.value = currentProjectWithTasks.tasks[taskIndex - 1]!.id;
}
}
if (currentHighlightedIndex === 0) {
// selected project is the first project in the list
// highlight the last project or the last task of the last project
const lastProject = filteredProjects.value[filteredProjects.value.length - 1];
const lastProject = filteredProjects.value[filteredProjects.value.length - 1]!;
if (lastProject.tasks.length > 0 && lastProject.expanded) {
// highlight last task of last project
highlightedItemId.value = lastProject.tasks[lastProject.tasks.length - 1].id;
highlightedItemId.value = lastProject.tasks[lastProject.tasks.length - 1]!.id;
} else {
highlightedItemId.value = filteredProjects.value[filteredProjects.value.length - 1].id;
highlightedItemId.value = filteredProjects.value[filteredProjects.value.length - 1]!.id;
}
} else {
// selected item is a project that is not the first project in the list
const previousProject = filteredProjects.value[currentHighlightedIndex - 1];
const previousProject = filteredProjects.value[currentHighlightedIndex - 1]!;
if (previousProject.tasks.length > 0 && previousProject.expanded) {
// highlight last task of previous project
highlightedItemId.value = previousProject.tasks[previousProject.tasks.length - 1].id;
highlightedItemId.value = previousProject.tasks[previousProject.tasks.length - 1]!.id;
} else {
highlightedItemId.value = filteredProjects.value[currentHighlightedIndex - 1].id;
highlightedItemId.value = filteredProjects.value[currentHighlightedIndex - 1]!.id;
}
}
}
@@ -361,33 +361,33 @@ function moveHighlightDown() {
const projectIndex = filteredProjects.value.indexOf(currentProjectWithTasks);
if (projectIndex === filteredProjects.value.length - 1) {
// highlight the first project if it was the last project
highlightedItemId.value = filteredProjects.value[0].id;
highlightedItemId.value = filteredProjects.value[0]!.id;
} else {
highlightedItemId.value = filteredProjects.value[projectIndex + 1].id;
highlightedItemId.value = filteredProjects.value[projectIndex + 1]!.id;
}
return;
}
highlightedItemId.value = currentProjectWithTasks.tasks[taskIndex + 1].id;
highlightedItemId.value = currentProjectWithTasks.tasks[taskIndex + 1]!.id;
}
}
if (currentHighlightedIndex === filteredProjects.value.length - 1) {
// selected project is the last project in the list
// highlight the first project or the last project of the last project
const lastProject = filteredProjects.value[filteredProjects.value.length - 1];
const lastProject = filteredProjects.value[filteredProjects.value.length - 1]!;
if (lastProject.tasks.length > 0 && lastProject.expanded) {
// highlight last task of last project
highlightedItemId.value = lastProject.tasks[0].id;
highlightedItemId.value = lastProject.tasks[0]!.id;
} else {
highlightedItemId.value = filteredProjects.value[0].id;
highlightedItemId.value = filteredProjects.value[0]!.id;
}
} else {
// selected item is a project that is not the last project in the list
const currentProjectWithTasks = filteredProjects.value[currentHighlightedIndex];
const currentProjectWithTasks = filteredProjects.value[currentHighlightedIndex]!;
if (currentProjectWithTasks.tasks.length > 0 && currentProjectWithTasks.expanded) {
// highlight last task of previous project
highlightedItemId.value = currentProjectWithTasks.tasks[0].id;
highlightedItemId.value = currentProjectWithTasks.tasks[0]!.id;
} else {
highlightedItemId.value = filteredProjects.value[currentHighlightedIndex + 1].id;
highlightedItemId.value = filteredProjects.value[currentHighlightedIndex + 1]!.id;
}
}
}
@@ -423,7 +423,7 @@ function expandProject() {
if (currentHighlightedIndex === -1) {
return;
}
const currentProject = filteredProjects.value[currentHighlightedIndex];
const currentProject = filteredProjects.value[currentHighlightedIndex]!;
currentProject.expanded = true;
}
@@ -434,7 +434,7 @@ function collapseProject() {
if (currentHighlightedIndex === -1) {
return;
}
const currentProject = filteredProjects.value[currentHighlightedIndex];
const currentProject = filteredProjects.value[currentHighlightedIndex]!;
currentProject.expanded = false;
}

View File

@@ -23,11 +23,11 @@ export const colors = [
];
export function getRandomColor() {
return colors[Math.floor(Math.random() * colors.length)];
return colors[Math.floor(Math.random() * colors.length)]!;
}
export function getRandomColorWithSeed(seed: string) {
const pseudoRandom = new Prando(seed);
const index = pseudoRandom.nextInt(0, colors.length - 1);
return colors[index];
return colors[index]!;
}

View File

@@ -14,8 +14,8 @@ export type NumberFormat =
export function formatNumber(value: number, format?: string): string {
// Convert to fixed 2 decimal places first
const parts = value.toFixed(2).split('.');
const wholePart = parts[0];
const decimalPart = parts[1];
const wholePart = parts[0] ?? '0';
const decimalPart = parts[1] ?? '00';
// Format the whole number part based on the format
let formattedWhole: string;

View File

@@ -107,7 +107,7 @@ export default class Prando {
* @return An item from the array.
*/
public nextArrayItem<T>(array: T[]): T {
return array[this.nextInt(0, array.length - 1)];
return array[this.nextInt(0, array.length - 1)] as T;
}
/**

View File

@@ -12,16 +12,16 @@ export function useSelectEvents<Type>(
const currentHightlightedIndex = filteredItems.value.indexOf(highlightedItem.value);
if (currentHightlightedIndex === 0) {
highlightedItemId.value = getKeyFromItem(
filteredItems.value[filteredItems.value.length - 1]
filteredItems.value[filteredItems.value.length - 1]!
);
} else {
highlightedItemId.value = getKeyFromItem(
filteredItems.value[currentHightlightedIndex - 1]
filteredItems.value[currentHightlightedIndex - 1]!
);
}
} else {
highlightedItemId.value = getKeyFromItem(
filteredItems.value[filteredItems.value.length - 1]
filteredItems.value[filteredItems.value.length - 1]!
);
}
}
@@ -30,14 +30,14 @@ export function useSelectEvents<Type>(
if (highlightedItem.value) {
const currentHightlightedIndex = filteredItems.value.indexOf(highlightedItem.value);
if (currentHightlightedIndex === filteredItems.value.length - 1) {
highlightedItemId.value = getKeyFromItem(filteredItems.value[0]);
highlightedItemId.value = getKeyFromItem(filteredItems.value[0]!);
} else {
highlightedItemId.value = getKeyFromItem(
filteredItems.value[currentHightlightedIndex + 1]
filteredItems.value[currentHightlightedIndex + 1]!
);
}
} else {
highlightedItemId.value = getKeyFromItem(filteredItems.value[0]);
highlightedItemId.value = getKeyFromItem(filteredItems.value[0]!);
}
}

View File

@@ -227,9 +227,9 @@ export function parseTimeInput(
if (HHMMSStimeRegex.test(input)) {
const match = input.match(HHMMSStimeRegex);
if (match) {
const hours = parseInt(match[1]);
const minutes = parseInt(match[2]);
const seconds = parseInt(match[3]);
const hours = parseInt(match[1]!);
const minutes = parseInt(match[2]!);
const seconds = parseInt(match[3]!);
return hours * 3600 + minutes * 60 + seconds;
}
}
@@ -239,8 +239,8 @@ export function parseTimeInput(
if (HHMMtimeRegex.test(input)) {
const match = input.match(HHMMtimeRegex);
if (match) {
const hours = parseInt(match[1]);
const minutes = parseInt(match[2]);
const hours = parseInt(match[1]!);
const minutes = parseInt(match[2]!);
return (hours * 60 + minutes) * 60;
}
}

View File

@@ -264,17 +264,17 @@ function prefetchProjectMembers(queryClient: QueryClient, projectId: string) {
*/
function findPrefetcher(url: string): ((queryClient: QueryClient) => void) | undefined {
// Extract pathname from URL
const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0];
const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0]!;
// Try exact match first
if (routePrefetchers[pathname]) {
if (pathname && routePrefetchers[pathname]) {
return routePrefetchers[pathname];
}
// Try pattern matching for dynamic routes like /projects/{id}
const projectMatch = pathname.match(/^\/projects\/([^/]+)$/);
const projectMatch = pathname?.match(/^\/projects\/([^/]+)$/);
if (projectMatch) {
const projectId = projectMatch[1];
const projectId = projectMatch[1]!;
return (queryClient) => {
prefetchProjects(queryClient);
prefetchTasks(queryClient);

View File

@@ -349,8 +349,8 @@ export function useCommandPalette() {
if (cmd.permission && !cmd.permission()) continue;
if (cmd.condition && !cmd.condition()) continue;
if (grouped[cmd.group]) {
grouped[cmd.group].push(cmd);
if (grouped[cmd.group] !== undefined) {
grouped[cmd.group]!.push(cmd);
}
}

View File

@@ -41,7 +41,7 @@ export function useTimeEntriesInfiniteQuery() {
if (!lastPage?.data || lastPage.data.length === 0) {
return undefined;
}
const latestTimeEntry = lastPage.data[lastPage.data.length - 1];
const latestTimeEntry = lastPage.data[lastPage.data.length - 1]!;
return dayjs(latestTimeEntry.start).utc().format();
},
enabled: computed(() => !!organizationId.value),