mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-18 13:12:16 +01:00
seperate project types, make tag dropdown location configurable, update api client
This commit is contained in:
@@ -218,7 +218,7 @@ const UserResource = z
|
|||||||
week_start: Weekday,
|
week_start: Weekday,
|
||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
const PersonalMemberResource = z
|
const PersonalMembershipResource = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
organization: z
|
organization: z
|
||||||
@@ -227,6 +227,7 @@ const PersonalMemberResource = z
|
|||||||
role: z.string(),
|
role: z.string(),
|
||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
const PersonalMembershipCollection = z.array(PersonalMembershipResource);
|
||||||
|
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
ClientResource,
|
ClientResource,
|
||||||
@@ -262,7 +263,8 @@ export const schemas = {
|
|||||||
TimeEntryUpdateRequest,
|
TimeEntryUpdateRequest,
|
||||||
Weekday,
|
Weekday,
|
||||||
UserResource,
|
UserResource,
|
||||||
PersonalMemberResource,
|
PersonalMembershipResource,
|
||||||
|
PersonalMembershipCollection,
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoints = makeApi([
|
const endpoints = makeApi([
|
||||||
@@ -2504,42 +2506,12 @@ If the group parameters are all set to `null` or are all missing, the
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/v1/users/me/members',
|
path: '/v1/users/me/memberships',
|
||||||
alias: 'getMyMemberships',
|
alias: 'getMyMemberships',
|
||||||
description: `This endpoint is independent of organization.`,
|
description: `This endpoint is independent of organization.`,
|
||||||
requestFormat: 'json',
|
requestFormat: 'json',
|
||||||
response: z
|
response: z
|
||||||
.object({
|
.object({ data: PersonalMembershipCollection })
|
||||||
data: z.array(PersonalMemberResource),
|
|
||||||
links: z
|
|
||||||
.object({
|
|
||||||
first: z.union([z.string(), z.null()]),
|
|
||||||
last: z.union([z.string(), z.null()]),
|
|
||||||
prev: z.union([z.string(), z.null()]),
|
|
||||||
next: z.union([z.string(), z.null()]),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
meta: z
|
|
||||||
.object({
|
|
||||||
current_page: z.number().int(),
|
|
||||||
from: z.union([z.number(), z.null()]),
|
|
||||||
last_page: z.number().int(),
|
|
||||||
links: z.array(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
url: z.union([z.string(), z.null()]),
|
|
||||||
label: z.string(),
|
|
||||||
active: z.boolean(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
),
|
|
||||||
path: z.union([z.string(), z.null()]),
|
|
||||||
per_page: z.number().int(),
|
|
||||||
to: z.union([z.number(), z.null()]),
|
|
||||||
total: z.number().int(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
.passthrough(),
|
||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
||||||
import type { BillableKey } from '@/utils/useProjects';
|
|
||||||
import Badge from '@/Components/Common/Badge.vue';
|
import Badge from '@/Components/Common/Badge.vue';
|
||||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||||
|
import type { BillableKey } from '@/types/projects';
|
||||||
|
|
||||||
const model = defineModel<BillableKey>({
|
const model = defineModel<BillableKey>({
|
||||||
default: 'default-rate',
|
default: 'default-rate',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
||||||
import type { BillableKey } from '@/utils/useProjects';
|
|
||||||
import Badge from '@/Components/Common/Badge.vue';
|
import Badge from '@/Components/Common/Badge.vue';
|
||||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||||
|
import type { BillableKey } from '@/types/projects';
|
||||||
|
|
||||||
const model = defineModel<BillableKey>({
|
const model = defineModel<BillableKey>({
|
||||||
default: 'non-billable',
|
default: 'non-billable',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import InputLabel from '@/Components/InputLabel.vue';
|
|||||||
import BillableRateInput from '@/Components/Common/BillableRateInput.vue';
|
import BillableRateInput from '@/Components/Common/BillableRateInput.vue';
|
||||||
import ProjectBillableSelect from '@/Components/Common/Project/ProjectBillableSelect.vue';
|
import ProjectBillableSelect from '@/Components/Common/Project/ProjectBillableSelect.vue';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import type { BillableKey } from '@/utils/useProjects';
|
import type { BillableKey } from '@/types/projects';
|
||||||
|
|
||||||
const billableRateSelect = ref<BillableKey>('non-billable');
|
const billableRateSelect = ref<BillableKey>('non-billable');
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,18 @@ import { type Component, computed, nextTick, ref, watch } from 'vue';
|
|||||||
import TagCreateModal from '@/Components/Common/Tag/TagCreateModal.vue';
|
import TagCreateModal from '@/Components/Common/Tag/TagCreateModal.vue';
|
||||||
import MultiselectDropdownItem from '@/Components/Common/MultiselectDropdownItem.vue';
|
import MultiselectDropdownItem from '@/Components/Common/MultiselectDropdownItem.vue';
|
||||||
import type { Tag } from '@/utils/api';
|
import type { Tag } from '@/utils/api';
|
||||||
|
import type { Placement } from '@floating-ui/vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
tags: Tag[];
|
defineProps<{
|
||||||
createTag: (name: string) => Promise<Tag | undefined>;
|
tags: Tag[];
|
||||||
}>();
|
createTag: (name: string) => Promise<Tag | undefined>;
|
||||||
|
align: Placement;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
align: 'bottom-start',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const model = defineModel<string[]>({
|
const model = defineModel<string[]>({
|
||||||
default: [],
|
default: [],
|
||||||
@@ -164,7 +171,7 @@ const showCreateTagModal = ref(false);
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
@submit="emit('submit')"
|
@submit="emit('submit')"
|
||||||
v-model="open"
|
v-model="open"
|
||||||
align="bottom-start"
|
:align="align"
|
||||||
:closeOnContentClick="false">
|
:closeOnContentClick="false">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<slot name="trigger"></slot>
|
<slot name="trigger"></slot>
|
||||||
@@ -180,7 +187,7 @@ const showCreateTagModal = ref(false);
|
|||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
|
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
|
||||||
placeholder="Search for a Tag..." />
|
placeholder="Search for a Tag..." />
|
||||||
<div ref="dropdownViewport" class="w-60">
|
<div ref="dropdownViewport" class="w-60 max-h-48 overflow-y-scroll">
|
||||||
<div
|
<div
|
||||||
v-for="tag in filteredTags"
|
v-for="tag in filteredTags"
|
||||||
:key="tag.id"
|
:key="tag.id"
|
||||||
@@ -197,18 +204,18 @@ const showCreateTagModal = ref(false);
|
|||||||
@click="toggleTag(tag.id)"
|
@click="toggleTag(tag.id)"
|
||||||
:name="tag.name"></MultiselectDropdownItem>
|
:name="tag.name"></MultiselectDropdownItem>
|
||||||
</div>
|
</div>
|
||||||
<div class="hover:bg-card-background-active rounded-b-lg">
|
</div>
|
||||||
<button
|
<div class="hover:bg-card-background-active rounded-b-lg">
|
||||||
@click="
|
<button
|
||||||
open = false;
|
@click="
|
||||||
showCreateTagModal = true;
|
open = false;
|
||||||
"
|
showCreateTagModal = true;
|
||||||
class="text-white flex space-x-3 items-center px-4 py-3 text-xs font-semibold border-t border-card-background-separator">
|
"
|
||||||
<PlusCircleIcon
|
class="text-white w-full flex space-x-3 items-center px-4 py-3 text-xs font-semibold border-t border-card-background-separator">
|
||||||
class="w-5 flex-shrink-0 text-icon-default"></PlusCircleIcon>
|
<PlusCircleIcon
|
||||||
<span>Create new Tag</span>
|
class="w-5 flex-shrink-0 text-icon-default"></PlusCircleIcon>
|
||||||
</button>
|
<span>Create new Tag</span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const timeEntryTags = computed<Tag[]>(() => {
|
|||||||
<template>
|
<template>
|
||||||
<TagDropdown
|
<TagDropdown
|
||||||
:tags="tags"
|
:tags="tags"
|
||||||
|
align="bottom-end"
|
||||||
:createTag
|
:createTag
|
||||||
@changed="emit('changed', model)"
|
@changed="emit('changed', model)"
|
||||||
v-model="model">
|
v-model="model">
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ function onToggleButtonPress(newState: boolean) {
|
|||||||
data-testid="dashboard_timer">
|
data-testid="dashboard_timer">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col sm:flex-row w-full justify-between rounded-lg bg-card-background border-card-border border transition shadow-card">
|
class="flex flex-col sm:flex-row w-full justify-between rounded-lg bg-card-background border-card-border border transition shadow-card">
|
||||||
<div class="flex items-center pr-6">
|
<div class="flex flex-1 items-center pr-6">
|
||||||
<input
|
<input
|
||||||
placeholder="What are you working on?"
|
placeholder="What are you working on?"
|
||||||
data-testid="time_entry_description"
|
data-testid="time_entry_description"
|
||||||
|
|||||||
1
resources/js/types/projects.d.ts
vendored
Normal file
1
resources/js/types/projects.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type BillableKey = 'non-billable' | 'default-rate' | 'custom-rate';
|
||||||
@@ -10,8 +10,6 @@ import type {
|
|||||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||||
import { useNotificationsStore } from '@/utils/notification';
|
import { useNotificationsStore } from '@/utils/notification';
|
||||||
|
|
||||||
export type BillableKey = 'non-billable' | 'default-rate' | 'custom-rate';
|
|
||||||
|
|
||||||
export const useProjectsStore = defineStore('projects', () => {
|
export const useProjectsStore = defineStore('projects', () => {
|
||||||
const projectResponse = ref<ProjectResponse | null>(null);
|
const projectResponse = ref<ProjectResponse | null>(null);
|
||||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user