make project and tags in mass updates resettable

This commit is contained in:
Gregor Vostrak
2024-10-09 14:20:07 +02:00
parent 26637e6f84
commit 129c132f97
3 changed files with 110 additions and 52 deletions

View File

@@ -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>

View File

@@ -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
) )
"> ">

View File

@@ -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>