mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-16 12:12:15 +01:00
Enable npm workspaces and update dependencies
This commit is contained in:
@@ -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
4134
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -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": {
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
) +
|
) +
|
||||||
': ' +
|
': ' +
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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'), {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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'] ??
|
||||||
|
'',
|
||||||
};
|
};
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
2164
resources/js/packages/api/package-lock.json
generated
2164
resources/js/packages/api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3925
resources/js/packages/ui/package-lock.json
generated
3925
resources/js/packages/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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]!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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]!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user