mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +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,
|
||||
})
|
||||
.passthrough();
|
||||
const PersonalMemberResource = z
|
||||
const PersonalMembershipResource = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization: z
|
||||
@@ -227,6 +227,7 @@ const PersonalMemberResource = z
|
||||
role: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
const PersonalMembershipCollection = z.array(PersonalMembershipResource);
|
||||
|
||||
export const schemas = {
|
||||
ClientResource,
|
||||
@@ -262,7 +263,8 @@ export const schemas = {
|
||||
TimeEntryUpdateRequest,
|
||||
Weekday,
|
||||
UserResource,
|
||||
PersonalMemberResource,
|
||||
PersonalMembershipResource,
|
||||
PersonalMembershipCollection,
|
||||
};
|
||||
|
||||
const endpoints = makeApi([
|
||||
@@ -2504,42 +2506,12 @@ If the group parameters are all set to `null` or are all missing, the
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/v1/users/me/members',
|
||||
path: '/v1/users/me/memberships',
|
||||
alias: 'getMyMemberships',
|
||||
description: `This endpoint is independent of organization.`,
|
||||
requestFormat: 'json',
|
||||
response: z
|
||||
.object({
|
||||
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(),
|
||||
})
|
||||
.object({ data: PersonalMembershipCollection })
|
||||
.passthrough(),
|
||||
errors: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
||||
import type { BillableKey } from '@/utils/useProjects';
|
||||
import Badge from '@/Components/Common/Badge.vue';
|
||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||
import type { BillableKey } from '@/types/projects';
|
||||
|
||||
const model = defineModel<BillableKey>({
|
||||
default: 'default-rate',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
||||
import type { BillableKey } from '@/utils/useProjects';
|
||||
import Badge from '@/Components/Common/Badge.vue';
|
||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||
import type { BillableKey } from '@/types/projects';
|
||||
|
||||
const model = defineModel<BillableKey>({
|
||||
default: 'non-billable',
|
||||
|
||||
@@ -3,7 +3,7 @@ import InputLabel from '@/Components/InputLabel.vue';
|
||||
import BillableRateInput from '@/Components/Common/BillableRateInput.vue';
|
||||
import ProjectBillableSelect from '@/Components/Common/Project/ProjectBillableSelect.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');
|
||||
|
||||
|
||||
@@ -5,11 +5,18 @@ import { type Component, computed, nextTick, ref, watch } from 'vue';
|
||||
import TagCreateModal from '@/Components/Common/Tag/TagCreateModal.vue';
|
||||
import MultiselectDropdownItem from '@/Components/Common/MultiselectDropdownItem.vue';
|
||||
import type { Tag } from '@/utils/api';
|
||||
import type { Placement } from '@floating-ui/vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tags: Tag[];
|
||||
createTag: (name: string) => Promise<Tag | undefined>;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
tags: Tag[];
|
||||
createTag: (name: string) => Promise<Tag | undefined>;
|
||||
align: Placement;
|
||||
}>(),
|
||||
{
|
||||
align: 'bottom-start',
|
||||
}
|
||||
);
|
||||
|
||||
const model = defineModel<string[]>({
|
||||
default: [],
|
||||
@@ -164,7 +171,7 @@ const showCreateTagModal = ref(false);
|
||||
<Dropdown
|
||||
@submit="emit('submit')"
|
||||
v-model="open"
|
||||
align="bottom-start"
|
||||
:align="align"
|
||||
:closeOnContentClick="false">
|
||||
<template #trigger>
|
||||
<slot name="trigger"></slot>
|
||||
@@ -180,7 +187,7 @@ const showCreateTagModal = ref(false);
|
||||
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"
|
||||
placeholder="Search for a Tag..." />
|
||||
<div ref="dropdownViewport" class="w-60">
|
||||
<div ref="dropdownViewport" class="w-60 max-h-48 overflow-y-scroll">
|
||||
<div
|
||||
v-for="tag in filteredTags"
|
||||
:key="tag.id"
|
||||
@@ -197,18 +204,18 @@ const showCreateTagModal = ref(false);
|
||||
@click="toggleTag(tag.id)"
|
||||
:name="tag.name"></MultiselectDropdownItem>
|
||||
</div>
|
||||
<div class="hover:bg-card-background-active rounded-b-lg">
|
||||
<button
|
||||
@click="
|
||||
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="w-5 flex-shrink-0 text-icon-default"></PlusCircleIcon>
|
||||
<span>Create new Tag</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hover:bg-card-background-active rounded-b-lg">
|
||||
<button
|
||||
@click="
|
||||
open = false;
|
||||
showCreateTagModal = true;
|
||||
"
|
||||
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">
|
||||
<PlusCircleIcon
|
||||
class="w-5 flex-shrink-0 text-icon-default"></PlusCircleIcon>
|
||||
<span>Create new Tag</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
||||
@@ -24,6 +24,7 @@ const timeEntryTags = computed<Tag[]>(() => {
|
||||
<template>
|
||||
<TagDropdown
|
||||
:tags="tags"
|
||||
align="bottom-end"
|
||||
:createTag
|
||||
@changed="emit('changed', model)"
|
||||
v-model="model">
|
||||
|
||||
@@ -78,7 +78,7 @@ function onToggleButtonPress(newState: boolean) {
|
||||
data-testid="dashboard_timer">
|
||||
<div
|
||||
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
|
||||
placeholder="What are you working on?"
|
||||
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 { useNotificationsStore } from '@/utils/notification';
|
||||
|
||||
export type BillableKey = 'non-billable' | 'default-rate' | 'custom-rate';
|
||||
|
||||
export const useProjectsStore = defineStore('projects', () => {
|
||||
const projectResponse = ref<ProjectResponse | null>(null);
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
|
||||
Reference in New Issue
Block a user