mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 04:32:15 +01:00
make project and tags in mass updates resettable
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
|||||||
} from '@/packages/api/src';
|
} from '@/packages/api/src';
|
||||||
import { useClientsStore } from '@/utils/useClients';
|
import { useClientsStore } from '@/utils/useClients';
|
||||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||||
import { Badge } from '@/packages/ui/src';
|
import { Badge, Checkbox } from '@/packages/ui/src';
|
||||||
import SelectDropdown from '../../../packages/ui/src/Input/SelectDropdown.vue';
|
import SelectDropdown from '../../../packages/ui/src/Input/SelectDropdown.vue';
|
||||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||||
import { useNotificationsStore } from '@/utils/notification';
|
import { useNotificationsStore } from '@/utils/notification';
|
||||||
@@ -71,7 +71,7 @@ watch(show, (value) => {
|
|||||||
|
|
||||||
const description = ref<string>('');
|
const description = ref<string>('');
|
||||||
const taskId = ref<string | null | undefined>(undefined);
|
const taskId = ref<string | null | undefined>(undefined);
|
||||||
const projectId = ref<string | null | undefined>(undefined);
|
const projectId = ref<string | null>(null);
|
||||||
const billable = ref<boolean | undefined>(undefined);
|
const billable = ref<boolean | undefined>(undefined);
|
||||||
const selectedTags = ref<string[]>([]);
|
const selectedTags = ref<string[]>([]);
|
||||||
|
|
||||||
@@ -106,19 +106,28 @@ async function submit() {
|
|||||||
if (description.value && description.value !== '') {
|
if (description.value && description.value !== '') {
|
||||||
timeEntryUpdatesBody.description = description.value;
|
timeEntryUpdatesBody.description = description.value;
|
||||||
}
|
}
|
||||||
if (projectId.value !== undefined) {
|
if (projectId.value !== null) {
|
||||||
timeEntryUpdatesBody.project_id = projectId.value;
|
if (projectId.value === '') {
|
||||||
|
// "No Project" is selected
|
||||||
|
timeEntryUpdatesBody.project_id = null;
|
||||||
|
} else {
|
||||||
|
timeEntryUpdatesBody.project_id = projectId.value;
|
||||||
|
}
|
||||||
timeEntryUpdatesBody.task_id = null;
|
timeEntryUpdatesBody.task_id = null;
|
||||||
|
if (taskId.value !== undefined) {
|
||||||
|
timeEntryUpdatesBody.task_id = taskId.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (taskId.value !== undefined) {
|
|
||||||
timeEntryUpdatesBody.task_id = taskId.value;
|
|
||||||
}
|
|
||||||
if (billable.value !== undefined) {
|
if (billable.value !== undefined) {
|
||||||
timeEntryUpdatesBody.billable = billable.value;
|
timeEntryUpdatesBody.billable = billable.value;
|
||||||
}
|
}
|
||||||
if (selectedTags.value.length > 0) {
|
if (selectedTags.value.length > 0) {
|
||||||
timeEntryUpdatesBody.tags = selectedTags.value;
|
timeEntryUpdatesBody.tags = selectedTags.value;
|
||||||
}
|
}
|
||||||
|
if (removeAllTags.value) {
|
||||||
|
timeEntryUpdatesBody.tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await handleApiRequestNotifications(
|
await handleApiRequestNotifications(
|
||||||
@@ -144,11 +153,12 @@ async function submit() {
|
|||||||
show.value = false;
|
show.value = false;
|
||||||
emit('submit');
|
emit('submit');
|
||||||
description.value = '';
|
description.value = '';
|
||||||
projectId.value = undefined;
|
projectId.value = null;
|
||||||
taskId.value = undefined;
|
taskId.value = undefined;
|
||||||
selectedTags.value = [];
|
selectedTags.value = [];
|
||||||
billable.value = undefined;
|
billable.value = undefined;
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
|
removeAllTags.value = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -156,6 +166,12 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const removeAllTags = ref(false);
|
||||||
|
watch(removeAllTags, () => {
|
||||||
|
if (removeAllTags.value) {
|
||||||
|
selectedTags.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -186,6 +202,8 @@ async function submit() {
|
|||||||
:createClient
|
:createClient
|
||||||
:currency="getOrganizationCurrencyString()"
|
:currency="getOrganizationCurrencyString()"
|
||||||
class="mt-1"
|
class="mt-1"
|
||||||
|
empty-placeholder="Select project..."
|
||||||
|
allow-reset
|
||||||
size="xlarge"
|
size="xlarge"
|
||||||
:projects="projects"
|
:projects="projects"
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
@@ -194,49 +212,65 @@ async function submit() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<InputLabel for="project" value="Tag" />
|
<InputLabel for="project" value="Tag" />
|
||||||
<TagDropdown :createTag v-model="selectedTags" :tags="tags">
|
<div class="flex space-x-5">
|
||||||
<template #trigger>
|
<TagDropdown
|
||||||
<Badge size="xlarge">
|
:createTag
|
||||||
<span v-if="selectedTags.length > 0">
|
v-model="selectedTags"
|
||||||
Set {{ selectedTags.length }} tags
|
:tags="tags">
|
||||||
</span>
|
<template #trigger>
|
||||||
<span v-else> Select Tags... </span>
|
<Badge
|
||||||
</Badge>
|
:disabled="removeAllTags"
|
||||||
</template>
|
tag="button"
|
||||||
</TagDropdown>
|
size="xlarge">
|
||||||
|
<span v-if="selectedTags.length > 0">
|
||||||
|
Set {{ selectedTags.length }} tags
|
||||||
|
</span>
|
||||||
|
<span v-else> Select Tags... </span>
|
||||||
|
</Badge>
|
||||||
|
</template>
|
||||||
|
</TagDropdown>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
v-model:checked="removeAllTags"
|
||||||
|
id="no_tags"></Checkbox>
|
||||||
|
<InputLabel for="no_tags" value="Remove all tags" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<InputLabel for="project" value="Billable" />
|
<InputLabel for="project" value="Billable" />
|
||||||
<SelectDropdown
|
<div class="flex">
|
||||||
v-model="timeEntryBillable"
|
<SelectDropdown
|
||||||
:get-key-from-item="(item) => item.value"
|
v-model="timeEntryBillable"
|
||||||
:get-name-for-item="(item) => item.label"
|
:get-key-from-item="(item) => item.value"
|
||||||
:items="[
|
:get-name-for-item="(item) => item.label"
|
||||||
{
|
:items="[
|
||||||
label: 'Keep current billable status',
|
{
|
||||||
value: 'do-not-update',
|
label: 'Keep current billable status',
|
||||||
},
|
value: 'do-not-update',
|
||||||
{
|
},
|
||||||
label: 'Billable',
|
{
|
||||||
value: 'billable',
|
label: 'Billable',
|
||||||
},
|
value: 'billable',
|
||||||
{
|
},
|
||||||
label: 'Non Billable',
|
{
|
||||||
value: 'non-billable',
|
label: 'Non Billable',
|
||||||
},
|
value: 'non-billable',
|
||||||
]">
|
},
|
||||||
<template v-slot:trigger>
|
]">
|
||||||
<Badge tag="button" size="xlarge">
|
<template v-slot:trigger>
|
||||||
<span v-if="billable === undefined">
|
<Badge tag="button" size="xlarge">
|
||||||
Set billable status
|
<span v-if="billable === undefined">
|
||||||
</span>
|
Set billable status
|
||||||
<span v-else-if="billable === true">
|
</span>
|
||||||
Billable
|
<span v-else-if="billable === true">
|
||||||
</span>
|
Billable
|
||||||
<span v-else> Non Billable </span></Badge
|
</span>
|
||||||
>
|
<span v-else> Non Billable </span></Badge
|
||||||
</template>
|
>
|
||||||
</SelectDropdown>
|
</template>
|
||||||
|
</SelectDropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const borderClasses = computed(() => {
|
|||||||
twMerge(
|
twMerge(
|
||||||
badgeClasses[size],
|
badgeClasses[size],
|
||||||
borderClasses,
|
borderClasses,
|
||||||
'rounded inline-flex items-center font-semibold text-white outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
|
'rounded inline-flex items-center font-semibold text-white disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
">
|
">
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import type {
|
|||||||
} from '@/packages/api/src';
|
} from '@/packages/api/src';
|
||||||
import ProjectBadge from '@/packages/ui/src/Project/ProjectBadge.vue';
|
import ProjectBadge from '@/packages/ui/src/Project/ProjectBadge.vue';
|
||||||
import Badge from '@/packages/ui/src/Badge.vue';
|
import Badge from '@/packages/ui/src/Badge.vue';
|
||||||
import { PlusIcon, PlusCircleIcon, MinusIcon } from '@heroicons/vue/16/solid';
|
import {
|
||||||
|
PlusIcon,
|
||||||
|
PlusCircleIcon,
|
||||||
|
MinusIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
} from '@heroicons/vue/16/solid';
|
||||||
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
||||||
|
|
||||||
const task = defineModel<string | null>('task', {
|
const task = defineModel<string | null>('task', {
|
||||||
@@ -56,10 +61,14 @@ const props = withDefaults(
|
|||||||
) => Promise<Project | undefined>;
|
) => Promise<Project | undefined>;
|
||||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
emptyPlaceholder: string;
|
||||||
|
allowReset: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
showBadgeBorder: true,
|
showBadgeBorder: true,
|
||||||
size: 'large',
|
size: 'large',
|
||||||
|
emptyPlaceholder: 'No Project',
|
||||||
|
allowReset: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -478,7 +487,13 @@ const currentTask = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedProjectName = computed(() => {
|
const selectedProjectName = computed(() => {
|
||||||
return currentProject.value?.name || 'No Project';
|
if (project.value === null) {
|
||||||
|
return props.emptyPlaceholder;
|
||||||
|
}
|
||||||
|
if (project.value === '') {
|
||||||
|
return 'No Project';
|
||||||
|
}
|
||||||
|
return currentProject.value?.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedProjectColor = computed(() => {
|
const selectedProjectColor = computed(() => {
|
||||||
@@ -535,7 +550,7 @@ const showCreateProject = ref(false);
|
|||||||
:border="showBadgeBorder"
|
:border="showBadgeBorder"
|
||||||
tag="button"
|
tag="button"
|
||||||
:name="selectedProjectName"
|
:name="selectedProjectName"
|
||||||
class="focus:border-border-tertiary w-full focus:outline-0 focus:bg-card-background-separator min-w-0">
|
class="focus:border-border-tertiary w-full focus:outline-0 focus:bg-card-background-separator min-w-0 relative">
|
||||||
<div class="flex items-center lg:space-x-1 min-w-0">
|
<div class="flex items-center lg:space-x-1 min-w-0">
|
||||||
<span class="whitespace-nowrap text-xs lg:text-sm">
|
<span class="whitespace-nowrap text-xs lg:text-sm">
|
||||||
{{ selectedProjectName }}
|
{{ selectedProjectName }}
|
||||||
@@ -549,6 +564,15 @@ const showCreateProject = ref(false);
|
|||||||
{{ currentTask.name }}
|
{{ currentTask.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="project !== null && allowReset"
|
||||||
|
@click.stop="
|
||||||
|
project = null;
|
||||||
|
task = null;
|
||||||
|
"
|
||||||
|
class="absolute right-0 top-0 h-full flex items-center pr-3 text-text-quaternary hover:text-text-secondary">
|
||||||
|
<XMarkIcon class="w-5"></XMarkIcon>
|
||||||
|
</button>
|
||||||
</ProjectBadge>
|
</ProjectBadge>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
|
|||||||
Reference in New Issue
Block a user