Enable npm workspaces and update dependencies

This commit is contained in:
Gregor Vostrak
2026-02-02 03:19:22 +01:00
parent 72662727c5
commit 1597b5490a
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 goToDashboard(page);
await page.getByTestId('time_entry_time').fill('asdasdasd'); 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( await page.locator(
'[data-testid="dashboard_timer"] [data-testid="timer_button"].bg-accent-300/70' '[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 }) => { 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, "private": true,
"type": "module", "type": "module",
"workspaces": [
"resources/js/packages/ui",
"resources/js/packages/api"
],
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
@@ -19,27 +23,29 @@
"@playwright/test": "^1.41.1", "@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/chroma-js": "2.4.5", "@types/chroma-js": "^3.1.0",
"@types/node": "^22.10.10", "@types/node": "^22.10.10",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.8.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.6.4", "axios": "^1.6.4",
"eslint-plugin-unused-imports": "^4.1.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", "openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-nesting": "^12.1.5", "postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"vite": "^6.0.11", "vite": "^7.0.0",
"vite-plugin-checker": "^0.8.0", "vite-plugin-checker": "^0.12.0",
"vue": "^3.5.0", "vue": "^3.5.0",
"vue-tsc": "^2.2.0" "vue-tsc": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.6.0", "@floating-ui/core": "^1.6.0",
"@floating-ui/vue": "^1.0.6", "@floating-ui/vue": "^1.0.6",
"@zodios/core": "^10.9.6",
"@fullcalendar/core": "^6.1.18", "@fullcalendar/core": "^6.1.18",
"@fullcalendar/daygrid": "^6.1.18", "@fullcalendar/daygrid": "^6.1.18",
"@fullcalendar/interaction": "^6.1.18", "@fullcalendar/interaction": "^6.1.18",
@@ -54,22 +60,23 @@
"@tanstack/vue-table": "^8.21.2", "@tanstack/vue-table": "^8.21.2",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0", "@vue/eslint-config-typescript": "^14.3.0",
"@vueuse/core": "^12.8.2", "@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^12.5.0", "@vueuse/integrations": "^14.0.0",
"chroma-js": "3.1.2", "chroma-js": "3.1.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "^5.5.0", "echarts": "^6.0.0",
"focus-trap": "^7.6.0", "focus-trap": "^8.0.0",
"lucide-vue-next": "^0.487.0", "lucide-vue-next": "^0.487.0",
"parse-duration": "^2.0.1", "parse-duration": "^2.0.1",
"pinia": "^2.1.7", "pinia": "^3.0.0",
"radix-vue": "^1.9.6", "radix-vue": "^1.9.6",
"reka-ui": "^2.7.0", "reka-ui": "^2.7.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vue-echarts": "^7.0.3" "vue-echarts": "^8.0.0",
"zod": "^3.23.8"
}, },
"overrides": { "overrides": {
"vite-plugin-checker": { "vite-plugin-checker": {

View File

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

View File

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

View File

@@ -23,9 +23,9 @@ const { data: last7Days, isLoading } = useQuery({
}, },
enabled: computed(() => !!organizationId.value), enabled: computed(() => !!organizationId.value),
placeholderData: Array.from({ length: 7 }, (_, i) => ({ 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, duration: 0,
history: Array(8).fill(0), history: Array(8).fill(0) as number[],
})), })),
}); });
</script> </script>

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ onMounted(() => {
const currentUrl = window.location.href; const currentUrl = window.location.href;
// check if # exists exactly once in the URL // check if # exists exactly once in the URL
if (currentUrl.split('#').length === 2) { 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) { if (entry.description === null) {
return { return {
value: entry.seconds, value: entry.seconds,
name: emptyPlaceholder[ name:
aggregatedTableTimeEntries.value?.grouped_type ?? 'project' emptyPlaceholder[
], aggregatedTableTimeEntries.value?.grouped_type ?? 'project'
] ?? '',
color: '#CCCCCC', color: '#CCCCCC',
}; };
} }
@@ -146,14 +147,17 @@ const tableData = computed(() => {
cost: entry.cost, cost: entry.cost,
description: description:
entry.description ?? entry.description ??
emptyPlaceholder[aggregatedTableTimeEntries.value?.grouped_type ?? 'project'], emptyPlaceholder[aggregatedTableTimeEntries.value?.grouped_type ?? 'project'] ??
'',
grouped_data: grouped_data:
entry.grouped_data?.map((el) => { entry.grouped_data?.map((el) => {
return { return {
seconds: el.seconds, seconds: el.seconds,
cost: el.cost, cost: el.cost,
description: 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'); addNotification('error', 'Please select the CSV or ZIP file that you want to import');
return; return;
} }
const rawBase64String = await toBase64(files.value[0]); const rawBase64String = await toBase64(files.value[0]!);
const base64String = rawBase64String.split(';')[1].replace('base64,', '') as string; const base64String = rawBase64String.split(';')[1]!.replace('base64,', '') as string;
const organizationId = getCurrentOrganizationId(); const organizationId = getCurrentOrganizationId();
if (organizationId !== null) { if (organizationId !== null) {
const { handleApiRequestNotifications } = useNotificationsStore(); const { handleApiRequestNotifications } = useNotificationsStore();

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"directory": "resources/js/packages/api" "directory": "resources/js/packages/api"
}, },
"scripts": { "scripts": {
"build": "npx vite build", "build": "vite build",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"files": [ "files": [
@@ -27,14 +27,14 @@
"type": "module", "type": "module",
"author": "solidtime", "author": "solidtime",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": {
"@zodios/core": "^10.9.6",
"axios": "^1.13.2",
"typescript": "^5.5.4",
"zod": "^3.23.8"
},
"devDependencies": { "devDependencies": {
"vite": "^5.4.2",
"vite-plugin-dts": "^4.0.3" "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", "author": "solidtime",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "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" "vite-plugin-dts": "^4.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"@floating-ui/vue": "^1.1.4", "@floating-ui/vue": "^1.1.4",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@vueuse/core": "^12.5.0", "@vitejs/plugin-vue": "^5.1.2 || ^6.0.0",
"@zodios/core": "^10.9.6", "@vueuse/core": "^12.5.0 || ^14.0.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
@@ -71,7 +62,9 @@
"reka-ui": "^2.2.0", "reka-ui": "^2.2.0",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"typescript": "^5.5.4",
"vite": "^5.4.1 || ^6.0.0 || ^7.0.0",
"vue": "^3.5.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'); const secondTime = secondSlot.getAttribute('data-time');
if (firstTime && secondTime) { if (firstTime && secondTime) {
const [h1, m1] = firstTime.split(':').map(Number); const [h1 = 0, m1 = 0] = firstTime.split(':').map(Number);
const [h2, m2] = secondTime.split(':').map(Number); const [h2 = 0, m2 = 0] = secondTime.split(':').map(Number);
const diff = h2 * 60 + m2 - (h1 * 60 + m1); const diff = h2 * 60 + m2 - (h1 * 60 + m1);
if (diff > 0) return diff; if (diff > 0) return diff;
} }

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <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 DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time'; import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time';
import dayjs from 'dayjs'; import dayjs from 'dayjs';

View File

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

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue'; 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 { formatDateLocalized, formatStartEnd } from '@/packages/ui/src/utils/time';
import TimeRangeSelector from '@/packages/ui/src/Input/TimeRangeSelector.vue'; import TimeRangeSelector from '@/packages/ui/src/Input/TimeRangeSelector.vue';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';

View File

@@ -4,7 +4,7 @@ import {
formatHumanReadableDuration, formatHumanReadableDuration,
parseTimeInput, parseTimeInput,
} from '@/packages/ui/src/utils/time'; } 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 dayjs from 'dayjs';
import type { Organization } from '@/packages/api/src'; import type { Organization } from '@/packages/api/src';

View File

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

View File

@@ -23,11 +23,11 @@ export const colors = [
]; ];
export function getRandomColor() { export function getRandomColor() {
return colors[Math.floor(Math.random() * colors.length)]; return colors[Math.floor(Math.random() * colors.length)]!;
} }
export function getRandomColorWithSeed(seed: string) { export function getRandomColorWithSeed(seed: string) {
const pseudoRandom = new Prando(seed); const pseudoRandom = new Prando(seed);
const index = pseudoRandom.nextInt(0, colors.length - 1); 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 { export function formatNumber(value: number, format?: string): string {
// Convert to fixed 2 decimal places first // Convert to fixed 2 decimal places first
const parts = value.toFixed(2).split('.'); const parts = value.toFixed(2).split('.');
const wholePart = parts[0]; const wholePart = parts[0] ?? '0';
const decimalPart = parts[1]; const decimalPart = parts[1] ?? '00';
// Format the whole number part based on the format // Format the whole number part based on the format
let formattedWhole: string; let formattedWhole: string;

View File

@@ -107,7 +107,7 @@ export default class Prando {
* @return An item from the array. * @return An item from the array.
*/ */
public nextArrayItem<T>(array: T[]): T { 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); const currentHightlightedIndex = filteredItems.value.indexOf(highlightedItem.value);
if (currentHightlightedIndex === 0) { if (currentHightlightedIndex === 0) {
highlightedItemId.value = getKeyFromItem( highlightedItemId.value = getKeyFromItem(
filteredItems.value[filteredItems.value.length - 1] filteredItems.value[filteredItems.value.length - 1]!
); );
} else { } else {
highlightedItemId.value = getKeyFromItem( highlightedItemId.value = getKeyFromItem(
filteredItems.value[currentHightlightedIndex - 1] filteredItems.value[currentHightlightedIndex - 1]!
); );
} }
} else { } else {
highlightedItemId.value = getKeyFromItem( 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) { if (highlightedItem.value) {
const currentHightlightedIndex = filteredItems.value.indexOf(highlightedItem.value); const currentHightlightedIndex = filteredItems.value.indexOf(highlightedItem.value);
if (currentHightlightedIndex === filteredItems.value.length - 1) { if (currentHightlightedIndex === filteredItems.value.length - 1) {
highlightedItemId.value = getKeyFromItem(filteredItems.value[0]); highlightedItemId.value = getKeyFromItem(filteredItems.value[0]!);
} else { } else {
highlightedItemId.value = getKeyFromItem( highlightedItemId.value = getKeyFromItem(
filteredItems.value[currentHightlightedIndex + 1] filteredItems.value[currentHightlightedIndex + 1]!
); );
} }
} else { } 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)) { if (HHMMSStimeRegex.test(input)) {
const match = input.match(HHMMSStimeRegex); const match = input.match(HHMMSStimeRegex);
if (match) { if (match) {
const hours = parseInt(match[1]); const hours = parseInt(match[1]!);
const minutes = parseInt(match[2]); const minutes = parseInt(match[2]!);
const seconds = parseInt(match[3]); const seconds = parseInt(match[3]!);
return hours * 3600 + minutes * 60 + seconds; return hours * 3600 + minutes * 60 + seconds;
} }
} }
@@ -239,8 +239,8 @@ export function parseTimeInput(
if (HHMMtimeRegex.test(input)) { if (HHMMtimeRegex.test(input)) {
const match = input.match(HHMMtimeRegex); const match = input.match(HHMMtimeRegex);
if (match) { if (match) {
const hours = parseInt(match[1]); const hours = parseInt(match[1]!);
const minutes = parseInt(match[2]); const minutes = parseInt(match[2]!);
return (hours * 60 + minutes) * 60; 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 { function findPrefetcher(url: string): ((queryClient: QueryClient) => void) | undefined {
// Extract pathname from URL // 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 // Try exact match first
if (routePrefetchers[pathname]) { if (pathname && routePrefetchers[pathname]) {
return routePrefetchers[pathname]; return routePrefetchers[pathname];
} }
// Try pattern matching for dynamic routes like /projects/{id} // Try pattern matching for dynamic routes like /projects/{id}
const projectMatch = pathname.match(/^\/projects\/([^/]+)$/); const projectMatch = pathname?.match(/^\/projects\/([^/]+)$/);
if (projectMatch) { if (projectMatch) {
const projectId = projectMatch[1]; const projectId = projectMatch[1]!;
return (queryClient) => { return (queryClient) => {
prefetchProjects(queryClient); prefetchProjects(queryClient);
prefetchTasks(queryClient); prefetchTasks(queryClient);

View File

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

View File

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