diff --git a/e2e/time.spec.ts b/e2e/time.spec.ts index e9b81382..11ef7ece 100644 --- a/e2e/time.spec.ts +++ b/e2e/time.spec.ts @@ -256,7 +256,7 @@ test('test that updating a the duration in the overview works on blur', async ({ await expect( newTimeEntry.getByTestId('time_entry_duration_input') - ).toHaveValue('00h 20min'); + ).toHaveValue('0h 20min'); }); // Test that start stop button stops running timer diff --git a/openapi.json.client.ts b/openapi.json.client.ts index cda97e89..f876523b 100644 --- a/openapi.json.client.ts +++ b/openapi.json.client.ts @@ -128,6 +128,7 @@ const TimeEntryResource = z description: z.union([z.string(), z.null()]), task_id: z.union([z.string(), z.null()]), project_id: z.union([z.string(), z.null()]), + organization_id: z.string(), user_id: z.string(), tags: z.array(z.string()), billable: z.boolean(), @@ -391,6 +392,17 @@ const endpoints = makeApi([ ], response: z.null(), errors: [ + { + status: 400, + description: `API exception`, + schema: z + .object({ + error: z.boolean(), + key: z.string(), + message: z.string(), + }) + .passthrough(), + }, { status: 403, description: `Authorization error`, @@ -623,6 +635,78 @@ const endpoints = makeApi([ }, ], }, + { + method: 'delete', + path: '/v1/organizations/:organization/invitations/:invitation', + alias: 'removeInvitation', + requestFormat: 'json', + parameters: [ + { + name: 'body', + type: 'Body', + schema: z.object({}).partial().passthrough(), + }, + { + name: 'organization', + type: 'Path', + schema: z.string().uuid(), + }, + { + name: 'invitation', + type: 'Path', + schema: z.string().uuid(), + }, + ], + response: z.null(), + errors: [ + { + status: 403, + description: `Authorization error`, + schema: z.object({ message: z.string() }).passthrough(), + }, + { + status: 404, + description: `Not found`, + schema: z.object({ message: z.string() }).passthrough(), + }, + ], + }, + { + method: 'post', + path: '/v1/organizations/:organization/invitations/:invitation/resend', + alias: 'resendInvitationEmail', + requestFormat: 'json', + parameters: [ + { + name: 'body', + type: 'Body', + schema: z.object({}).partial().passthrough(), + }, + { + name: 'organization', + type: 'Path', + schema: z.string().uuid(), + }, + { + name: 'invitation', + type: 'Path', + schema: z.string().uuid(), + }, + ], + response: z.null(), + errors: [ + { + status: 403, + description: `Authorization error`, + schema: z.object({ message: z.string() }).passthrough(), + }, + { + status: 404, + description: `Not found`, + schema: z.object({ message: z.string() }).passthrough(), + }, + ], + }, { method: 'get', path: '/v1/organizations/:organization/members', @@ -761,6 +845,17 @@ const endpoints = makeApi([ ], response: z.null(), errors: [ + { + status: 400, + description: `API exception`, + schema: z + .object({ + error: z.boolean(), + key: z.string(), + message: z.string(), + }) + .passthrough(), + }, { status: 403, description: `Authorization error`, @@ -1102,6 +1197,17 @@ const endpoints = makeApi([ ], response: z.null(), errors: [ + { + status: 400, + description: `API exception`, + schema: z + .object({ + error: z.boolean(), + key: z.string(), + message: z.string(), + }) + .passthrough(), + }, { status: 403, description: `Authorization error`, @@ -1371,6 +1477,17 @@ const endpoints = makeApi([ ], response: z.null(), errors: [ + { + status: 400, + description: `API exception`, + schema: z + .object({ + error: z.boolean(), + key: z.string(), + message: z.string(), + }) + .passthrough(), + }, { status: 403, description: `Authorization error`, @@ -1567,6 +1684,17 @@ const endpoints = makeApi([ ], response: z.null(), errors: [ + { + status: 400, + description: `API exception`, + schema: z + .object({ + error: z.boolean(), + key: z.string(), + message: z.string(), + }) + .passthrough(), + }, { status: 403, description: `Authorization error`, diff --git a/resources/js/Components/Common/Member/MemberCombobox.vue b/resources/js/Components/Common/Member/MemberCombobox.vue index 170a73ef..83714877 100644 --- a/resources/js/Components/Common/Member/MemberCombobox.vue +++ b/resources/js/Components/Common/Member/MemberCombobox.vue @@ -79,7 +79,6 @@ function updateSearchValue(event: Event) { const emit = defineEmits(['update:modelValue', 'changed']); function updateMember(newValue: string | null) { - console.log(newValue); if (newValue) { model.value = newValue; nextTick(() => { diff --git a/resources/js/Components/Common/Tag/TagDropdown.vue b/resources/js/Components/Common/Tag/TagDropdown.vue index a462171c..56c8e1b8 100644 --- a/resources/js/Components/Common/Tag/TagDropdown.vue +++ b/resources/js/Components/Common/Tag/TagDropdown.vue @@ -136,7 +136,6 @@ function moveHighlightDown() { filteredTags.value[currentHightlightedIndex + 1].id; } } - console.log('move down'); } const highlightedItemId = ref(null); diff --git a/resources/js/Components/OrganizationSwitcher.vue b/resources/js/Components/OrganizationSwitcher.vue index 0d45a7c0..e8131ba1 100644 --- a/resources/js/Components/OrganizationSwitcher.vue +++ b/resources/js/Components/OrganizationSwitcher.vue @@ -2,11 +2,11 @@ import { ChevronDownIcon } from '@heroicons/vue/20/solid'; import Dropdown from '@/Components/Dropdown.vue'; import DropdownLink from '@/Components/DropdownLink.vue'; -import { router, usePage } from '@inertiajs/vue3'; +import { usePage } from '@inertiajs/vue3'; import type { Organization, User } from '@/types/models'; import { isBillingActivated } from '@/utils/billing'; -import { initializeStores } from '@/utils/init'; import { canUpdateOrganization } from '@/utils/permissions'; +import { switchOrganization } from '@/utils/useOrganization'; const page = usePage<{ jetstream: { @@ -22,19 +22,8 @@ const page = usePage<{ }; }>(); -const switchToTeam = (team: Organization) => { - router.put( - route('current-team.update'), - { - team_id: team.id, - }, - { - preserveState: false, - onSuccess: () => { - initializeStores(); - }, - } - ); +const switchToTeam = (organization: Organization) => { + switchOrganization(organization.id); }; diff --git a/resources/js/Components/TimeTracker.vue b/resources/js/Components/TimeTracker.vue index aec48068..ef4907a7 100644 --- a/resources/js/Components/TimeTracker.vue +++ b/resources/js/Components/TimeTracker.vue @@ -15,6 +15,9 @@ import { storeToRefs } from 'pinia'; import parse from 'parse-duration'; import TimeTrackerTagDropdown from '@/Components/Common/TimeTracker/TimeTrackerTagDropdown.vue'; import TimeTrackerProjectTaskDropdown from '@/Components/Common/TimeTracker/TimeTrackerProjectTaskDropdown.vue'; +import { getCurrentOrganizationId } from '@/utils/useUser'; +import { switchOrganization } from '@/utils/useOrganization'; +import SecondaryButton from '@/Components/SecondaryButton.vue'; const page = usePage<{ auth: { @@ -148,62 +151,93 @@ function onTimeEntryEnterPress() { const activeElement = document.activeElement as HTMLElement; activeElement?.blur(); } + +const isRunningInDifferentOrganization = computed(() => { + return ( + currentTimeEntry.value.organization_id && + getCurrentOrganizationId() && + currentTimeEntry.value.organization_id !== getCurrentOrganizationId() + ); +}); + +function switchToTimeEntryOrganization() { + if (currentTimeEntry.value.organization_id) { + switchOrganization(currentTimeEntry.value.organization_id); + } +} diff --git a/resources/js/utils/permissions.ts b/resources/js/utils/permissions.ts index 6bd0f0ab..30a22373 100644 --- a/resources/js/utils/permissions.ts +++ b/resources/js/utils/permissions.ts @@ -7,7 +7,6 @@ const page = usePage<{ }>(); function currentUserHasPermission(permission: string) { - console.log(page.props.auth.permissions); if (Array.isArray(page.props.auth.permissions)) { return page.props.auth.permissions.includes(permission); } diff --git a/resources/js/utils/useCurrentTimeEntry.ts b/resources/js/utils/useCurrentTimeEntry.ts index 0d2c2172..214803fa 100644 --- a/resources/js/utils/useCurrentTimeEntry.ts +++ b/resources/js/utils/useCurrentTimeEntry.ts @@ -22,6 +22,7 @@ const emptyTimeEntry = { project_id: null, tags: [], billable: false, + organization_id: '', } as TimeEntry; export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => { @@ -56,20 +57,10 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => { async function fetchCurrentTimeEntry() { const organizationId = getCurrentOrganizationId(); if (organizationId) { - const timeEntriesResponse = await handleApiRequestNotifications( - api.getTimeEntries({ - queries: { - active: 'true', - user_id: getCurrentUserId(), - }, - params: { - organization: organizationId, - }, - }) - ); + const timeEntriesResponse = await api.getMyActiveTimeEntry({}); if (timeEntriesResponse?.data) { - if (timeEntriesResponse.data.length === 1) { - currentTimeEntry.value = timeEntriesResponse.data[0]; + if (timeEntriesResponse.data) { + currentTimeEntry.value = timeEntriesResponse.data; } else { currentTimeEntry.value = { ...emptyTimeEntry }; } diff --git a/resources/js/utils/useOrganization.ts b/resources/js/utils/useOrganization.ts new file mode 100644 index 00000000..d3e72c78 --- /dev/null +++ b/resources/js/utils/useOrganization.ts @@ -0,0 +1,17 @@ +import { router } from '@inertiajs/vue3'; +import { initializeStores } from '@/utils/init'; + +export function switchOrganization(organizationId: string) { + router.put( + route('current-team.update'), + { + team_id: organizationId, + }, + { + preserveState: false, + onSuccess: () => { + initializeStores(); + }, + } + ); +}