change color palette, change user_id to member_id

This commit is contained in:
Gregor Vostrak
2024-05-21 01:53:50 +02:00
parent 68ecc1227d
commit 8b12dec546
51 changed files with 836 additions and 388 deletions

View File

@@ -37,10 +37,10 @@ const borderClasses = computed(() => {
:is="tag"
:class="
twMerge(
props.class,
badgeClasses[size],
borderClasses,
'rounded inline-flex items-center font-semibold text-white'
'rounded inline-flex items-center font-semibold text-white',
props.class
)
">
<slot></slot>

View File

@@ -6,6 +6,10 @@ import {
getOrganizationCurrencySymbol,
} from '../../utils/money';
defineProps<{
name: string;
}>();
const model = defineModel({
default: null,
type: Number,
@@ -51,13 +55,14 @@ function formatCents(modelValue: number) {
<template>
<div class="relative">
<TextInput
id="projectMemberRate"
:id="name"
ref="projectMemberRateInput"
:modelValue="formatCents(model)"
@blur="updateRate($event.target.value)"
type="text"
:name="name"
placeholder="Billable Rate"
class="mt-1 block w-full"
class="mt-2 block w-full"
autocomplete="teamMemberRate" />
<span>
<div

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { twMerge } from 'tailwind-merge';
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
const active = defineModel({ default: false });
const emit = defineEmits(['changed']);
function toggleBillable() {
@@ -19,7 +20,7 @@ const props = withDefaults(
const iconColorClasses = computed(() => {
if (active.value) {
return 'text-accent-200/80 focus:text-accent-200 hover:text-accent-200';
return 'text-accent-300 focus:text-accent-200 hover:text-accent-200';
} else {
return 'text-icon-default focus:text-icon-active hover:text-icon-active';
}
@@ -49,18 +50,7 @@ const iconSizeWrapperClasses =
'flex-shrink-0 ring-0 focus:outline-none focus:ring-0 transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center'
)
">
<svg
:class="iconSizeClasses"
viewBox="0 0 8 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4 1V13M1 10.182L1.879 10.841C3.05 11.72 4.949 11.72 6.121 10.841C7.293 9.962 7.293 8.538 6.121 7.659C5.536 7.219 4.768 7 4 7C3.275 7 2.55 6.78 1.997 6.341C0.891 5.462 0.891 4.038 1.997 3.159C3.103 2.28 4.897 2.28 6.003 3.159L6.418 3.489"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<BillableIcon :class="iconSizeClasses"></BillableIcon>
</button>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import { twMerge } from 'tailwind-merge';
import { computed } from 'vue';
const props = withDefaults(
defineProps<{
expanded?: boolean;
size: string;
}>(),
{
expanded: false,
size: 'w-7 h-7',
}
);
const expandedStatusClasses = computed(() => {
if (props.expanded) {
return 'border-card-border border bg-card-background-active text-white';
}
return 'border-card-border border bg-card-background text-muted';
});
</script>
<template>
<button
:class="
twMerge(
'font-medium rounded flex items-center transition justify-center',
expandedStatusClasses,
props.size
)
">
<slot></slot>
</button>
</template>
<style scoped></style>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts"></script>
<template>
<svg viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4 1V13M1 10.182L1.879 10.841C3.05 11.72 4.949 11.72 6.121 10.841C7.293 9.962 7.293 8.538 6.121 7.659C5.536 7.219 4.768 7 4 7C3.275 7 2.55 6.78 1.997 6.341C0.891 5.462 0.891 4.038 1.997 3.159C3.103 2.28 4.897 2.28 6.003 3.159L6.418 3.489"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</template>
<style scoped></style>

View File

@@ -37,7 +37,7 @@ const filteredMembers = computed(() => {
.toLowerCase()
.includes(searchValue.value?.toLowerCase()?.trim() || '') &&
!props.hiddenMembers.some(
(hiddenMember) => hiddenMember.user_id === member.user_id
(hiddenMember) => hiddenMember.id === member.id
) &&
member.is_placeholder === false
);
@@ -54,7 +54,7 @@ onMounted(() => {
function resetHighlightedItem() {
if (filteredMembers.value.length > 0) {
highlightedItemId.value = filteredMembers.value[0].user_id;
highlightedItemId.value = filteredMembers.value[0].id;
}
}
@@ -65,10 +65,10 @@ function updateSearchValue(event: Event) {
const highlightedClientId = highlightedItemId.value;
if (highlightedClientId) {
const highlightedClient = members.value.find(
(member) => member.user_id === highlightedClientId
(member) => member.id === highlightedClientId
);
if (highlightedClient) {
model.value = highlightedClient.user_id;
model.value = highlightedClient.id;
}
}
} else {
@@ -94,10 +94,10 @@ function moveHighlightUp() {
);
if (currentHightlightedIndex === 0) {
highlightedItemId.value =
filteredMembers.value[filteredMembers.value.length - 1].user_id;
filteredMembers.value[filteredMembers.value.length - 1].id;
} else {
highlightedItemId.value =
filteredMembers.value[currentHightlightedIndex - 1].user_id;
filteredMembers.value[currentHightlightedIndex - 1].id;
}
}
}
@@ -108,10 +108,10 @@ function moveHighlightDown() {
highlightedItem.value
);
if (currentHightlightedIndex === filteredMembers.value.length - 1) {
highlightedItemId.value = filteredMembers.value[0].user_id;
highlightedItemId.value = filteredMembers.value[0].id;
} else {
highlightedItemId.value =
filteredMembers.value[currentHightlightedIndex + 1].user_id;
filteredMembers.value[currentHightlightedIndex + 1].id;
}
}
}
@@ -119,14 +119,13 @@ function moveHighlightDown() {
const highlightedItemId = ref<string | null>(null);
const highlightedItem = computed(() => {
return members.value.find(
(member) => member.user_id === highlightedItemId.value
(member) => member.id === highlightedItemId.value
);
});
const currentValue = computed(() => {
if (model.value) {
return members.value.find((member) => member.user_id === model.value)
?.name;
return members.value.find((member) => member.id === model.value)?.name;
}
return searchValue.value;
});
@@ -186,18 +185,18 @@ function onUnfocus() {
</div>
<div
v-for="member in filteredMembers"
:key="member.user_id"
:key="member.id"
role="option"
:value="member.user_id"
:value="member.id"
:class="{
'bg-card-background-active':
member.user_id === highlightedItemId,
member.id === highlightedItemId,
}"
@click="updateMember(member.user_id)"
@click="updateMember(member.id)"
data-testid="client_dropdown_entries"
:data-client-id="member.user_id">
:data-client-id="member.id">
<ClientDropdownItem
:selected="isMemberSelected(member.user_id)"
:selected="isMemberSelected(member.id)"
:name="member.name"></ClientDropdownItem>
</div>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import MultiselectDropdown from '@/Components/Common/MultiselectDropdown.vue';
import { useMembersStore } from '@/utils/useMembers';
import { storeToRefs } from 'pinia';
import type { Member } from '@/utils/api';
const membersStore = useMembersStore();
const { members } = storeToRefs(membersStore);
function getKeyFromItem(item: Member) {
return item.id;
}
function getNameForItem(item: Member) {
return item.name;
}
</script>
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Member..."
:items="members"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">
<template #trigger>
<slot name="trigger"></slot>
</template>
</MultiselectDropdown>
</template>

View File

@@ -29,7 +29,7 @@ async function invitePlaceholder(id: string) {
{
params: {
organization: organizationId,
membership: id,
member: id,
},
}
),

View File

@@ -6,15 +6,17 @@ const model = defineModel<string>({ default: '' });
</script>
<template>
<div class="px-3">
<div>
<Dropdown align="bottom">
<template #trigger>
<div
:style="{
backgroundColor: model,
boxShadow: `var(--tw-ring-inset) 0 0 0 calc(5px + var(--tw-ring-offset-width)) ${model}30`,
}"
class="w-4 h-4 rounded-full cursor-pointer"></div>
<button
class="p-2 bg-input-background hover:bg-tertiary transition rounded-full border border-input-border">
<div
:style="{
backgroundColor: model,
}"
class="w-5 h-5 rounded-full cursor-pointer"></div>
</button>
</template>
<template #content>
<div class="text-white grid grid-cols-6 gap-3 px-3 py-3">

View File

@@ -9,12 +9,13 @@ import PrimaryButton from '@/Components/PrimaryButton.vue';
import { useProjectsStore } from '@/utils/useProjects';
import { useFocus } from '@vueuse/core';
import ClientDropdown from '@/Components/Common/Client/ClientDropdown.vue';
import { twMerge } from 'tailwind-merge';
import Badge from '@/Components/Common/Badge.vue';
import { useClientsStore } from '@/utils/useClients';
import { storeToRefs } from 'pinia';
import ProjectColorSelector from '@/Components/Common/Project/ProjectColorSelector.vue';
import BillableRateInput from '@/Components/Common/BillableRateInput.vue';
import { UserCircleIcon } from '@heroicons/vue/20/solid';
import InputLabel from '@/Components/InputLabel.vue';
const { createProject } = useProjectsStore();
const { clients } = storeToRefs(useClientsStore());
@@ -63,33 +64,47 @@ const currentClientName = computed(() => {
<div
class="sm:flex items-center space-y-2 sm:space-y-0 sm:space-x-4">
<div class="flex-1 flex items-center">
<ProjectColorSelector
v-model="project.color"></ProjectColorSelector>
<TextInput
id="projectName"
ref="projectNameInput"
v-model="project.name"
type="text"
placeholder="Project Name"
@keydown.enter="submit()"
class="mt-1 block w-full"
required
autocomplete="projectName" />
<div class="text-center pr-5">
<InputLabel for="color" value="Color" />
<ProjectColorSelector
class="mt-2.5"
v-model="project.color"></ProjectColorSelector>
</div>
<div class="w-full">
<InputLabel for="projectName" value="Project name" />
<TextInput
id="projectName"
name="projectName"
ref="projectNameInput"
v-model="project.name"
type="text"
placeholder="The next big thing"
@keydown.enter="submit()"
class="mt-2 block w-full"
required
autocomplete="projectName" />
</div>
</div>
<div class="sm:max-w-[120px]">
<BillableRateInput v-model="project.billable_rate" />
<InputLabel for="billableRate" value="Billable Rate" />
<BillableRateInput
v-model="project.billable_rate"
name="billableRate" />
</div>
<div>
<ClientDropdown v-model="project.client_id">
<InputLabel for="client" value="Client" />
<ClientDropdown class="mt-2" v-model="project.client_id">
<template #trigger>
<Badge size="large">
<div
:class="
twMerge('inline-block rounded-full')
"></div>
<span>
{{ currentClientName }}
</span>
<Badge
class="bg-input-background cursor-pointer hover:bg-tertiary"
size="xlarge">
<div class="flex items-center space-x-2">
<UserCircleIcon
class="w-5 text-icon-default"></UserCircleIcon>
<span>
{{ currentClientName }}
</span>
</div>
</Badge>
</template>
</ClientDropdown>
@@ -97,7 +112,7 @@ const currentClientName = computed(() => {
</div>
</template>
<template #footer>
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>
<PrimaryButton
class="ms-3"

View File

@@ -78,7 +78,9 @@ const currentClientName = computed(() => {
autocomplete="projectName" />
</div>
<div class="sm:max-w-[120px]">
<BillableRateInput v-model="project.billable_rate" />
<BillableRateInput
v-model="project.billable_rate"
name="billable_rate" />
</div>
<div class="">
<ClientDropdown v-model="project.client_id">

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import MultiselectDropdown from '@/Components/Common/MultiselectDropdown.vue';
import { storeToRefs } from 'pinia';
import { useProjectsStore } from '@/utils/useProjects';
import type { Project } from '@/utils/api';
const projectsStore = useProjectsStore();
const { projects } = storeToRefs(projectsStore);
function getKeyFromItem(item: Project) {
return item.id;
}
function getNameForItem(item: Project) {
return item.name;
}
</script>
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Project..."
:items="projects"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">
<template #trigger>
<slot name="trigger"></slot>
</template>
</MultiselectDropdown>
</template>

View File

@@ -18,7 +18,7 @@ const props = defineProps<{
}>();
const projectMember = ref<CreateProjectMemberBody>({
user_id: '',
member_id: '',
billable_rate: null,
});
@@ -26,7 +26,7 @@ async function submit() {
await createProjectMember(props.projectId, projectMember.value);
show.value = false;
projectMember.value = {
user_id: '',
member_id: '',
billable_rate: null,
};
}
@@ -49,10 +49,11 @@ useFocus(projectNameInput, { initialValue: true });
<div class="col-span-3 sm:col-span-2">
<MemberCombobox
:hidden-members="props.existingMembers"
v-model="projectMember.user_id"></MemberCombobox>
v-model="projectMember.member_id"></MemberCombobox>
</div>
<div class="col-span-3 sm:col-span-1 flex-1">
<BillableRateInput
name="billable_rate"
v-model="
projectMember.billable_rate
"></BillableRateInput>

View File

@@ -22,7 +22,7 @@ function deleteProjectMember() {
const { members } = storeToRefs(useMembersStore());
const member = computed(() => {
return members.value.find(
(member) => member.user_id === props.projectMember.user_id
(member) => member.id === props.projectMember.member_id
);
});
</script>

View File

@@ -14,13 +14,13 @@ import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
import TimeEntryMoreOptionsDropdown from '@/Components/Common/TimeEntry/TimeEntryMoreOptionsDropdown.vue';
import TimeTrackerProjectTaskDropdown from '@/Components/Common/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
import BillableToggleButton from '@/Components/Common/BillableToggleButton.vue';
import { computed, ref } from 'vue';
import { twMerge } from 'tailwind-merge';
import { ref } from 'vue';
import {
formatHumanReadableDuration,
formatStartEnd,
} from '../../../utils/time';
import TimeEntryRow from '@/Components/Common/TimeEntry/TimeEntryRow.vue';
import GroupedItemsCountButton from '@/Components/Common/GroupedItemsCountButton.vue';
const currentTimeEntryStore = useCurrentTimeEntryStore();
const { stopTimer } = currentTimeEntryStore;
@@ -99,13 +99,6 @@ function updateProjectAndTask(projectId: string, taskId: string) {
}
const expanded = ref(false);
const expandedStatusClasses = computed(() => {
if (expanded.value) {
return 'border-card-border border bg-card-background-active text-white';
}
return 'border-card-border border bg-card-background text-muted';
});
</script>
<template>
@@ -118,18 +111,11 @@ const expandedStatusClasses = computed(() => {
<input
type="checkbox"
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:ring-accent-500/80" />
<button
@click="expanded = !expanded"
:class="
twMerge(
expandedStatusClasses,
'font-medium w-7 h-7 rounded flex items-center transition justify-center'
)
">
<span>
{{ timeEntry?.timeEntries?.length }}
</span>
</button>
<GroupedItemsCountButton
:expanded="expanded"
@click="expanded = !expanded">
{{ timeEntry?.timeEntries?.length }}
</GroupedItemsCountButton>
<TimeEntryDescriptionInput
@changed="updateTimeEntryDescription"
class="flex-1"

View File

@@ -19,7 +19,7 @@ function onChange(event: Event) {
@blur="onChange"
@keydown.enter="onChange"
placeholder="Add a description"
class="text-white placeholder-muted font-medium bg-transparent hover:bg-card-background rounded-lg border border-transparent hover:border-card-border" />
class="text-white font-medium bg-transparent focus-visible:ring-0 hover:bg-card-background rounded-lg border border-transparent hover:border-card-border" />
</label>
</div>
</template>

View File

@@ -21,7 +21,9 @@ const timeEntryTags = computed<Tag[]>(() => {
<template>
<TagDropdown @changed="emit('changed', model)" v-model="model">
<template #trigger>
<button data-testid="time_entry_tag_dropdown">
<button
data-testid="time_entry_tag_dropdown"
class="opacity-50 group-hover:opacity-100 transition">
<TagBadge
:border="false"
size="large"

View File

@@ -8,6 +8,9 @@ import { useTasksStore } from '@/utils/useTasks';
import ProjectDropdownItem from '@/Components/Common/Project/ProjectDropdownItem.vue';
import type { Project, Task } from '@/utils/api';
import ProjectBadge from '@/Components/Common/Project/ProjectBadge.vue';
import Badge from '@/Components/Common/Badge.vue';
import { PlusIcon } from '@heroicons/vue/16/solid';
import ProjectCreateModal from '@/Components/Common/Project/ProjectCreateModal.vue';
const projectStore = useProjectsStore();
const { projects } = storeToRefs(projectStore);
@@ -94,9 +97,7 @@ const filteredProjects = computed(() => {
});
async function addClientIfNoneExists() {
if (highlightedItemId.value) {
setProjectAndClientBasedOnHighlightedItem();
}
setProjectAndClientBasedOnHighlightedItem();
}
function isProjectSelected(project: Project) {
@@ -142,131 +143,122 @@ function updateSearchValue(event: Event) {
const emit = defineEmits(['update:modelValue', 'changed']);
function moveHighlightUp() {
if (highlightedItemId.value) {
const currentHighlightedIndex = filteredProjects.value.findIndex(
const currentHighlightedIndex = filteredProjects.value.findIndex(
(projectWithTasks) =>
projectWithTasks.project.id === highlightedItemId.value
);
// check if it is a project id
if (currentHighlightedIndex === -1) {
// the ID is a task ID
const currentProjectWithTasks = filteredProjects.value.find(
(projectWithTasks) =>
projectWithTasks.project.id === highlightedItemId.value
);
// check if it is a project id
if (currentHighlightedIndex === -1) {
// the ID is a task ID
const currentProjectWithTasks = filteredProjects.value.find(
(projectWithTasks) =>
projectWithTasks.tasks.some(
(task) => task.id === highlightedItemId.value
)
);
if (currentProjectWithTasks) {
const taskIndex = currentProjectWithTasks.tasks.findIndex(
projectWithTasks.tasks.some(
(task) => task.id === highlightedItemId.value
);
if (taskIndex === -1) {
return;
}
if (taskIndex === 0) {
// highlight the project if it was the first task before
highlightedItemId.value =
currentProjectWithTasks.project.id;
return;
}
highlightedItemId.value =
currentProjectWithTasks.tasks[taskIndex - 1].id;
)
);
if (currentProjectWithTasks) {
const taskIndex = currentProjectWithTasks.tasks.findIndex(
(task) => task.id === highlightedItemId.value
);
if (taskIndex === -1) {
return;
}
if (taskIndex === 0) {
// highlight the project if it was the first task before
highlightedItemId.value = currentProjectWithTasks.project.id;
return;
}
highlightedItemId.value =
currentProjectWithTasks.tasks[taskIndex - 1].id;
}
if (currentHighlightedIndex === 0) {
// highlight the last project or the last project of the last project
const lastProject =
filteredProjects.value[filteredProjects.value.length - 1];
if (lastProject.tasks.length > 0) {
// highlight last task of last project
highlightedItemId.value =
lastProject.tasks[lastProject.tasks.length - 1].id;
} else {
highlightedItemId.value =
filteredProjects.value[
filteredProjects.value.length - 1
].project.id;
}
}
if (currentHighlightedIndex === 0) {
// highlight the last project or the last project of the last project
const lastProject =
filteredProjects.value[filteredProjects.value.length - 1];
if (lastProject.tasks.length > 0) {
// highlight last task of last project
highlightedItemId.value =
lastProject.tasks[lastProject.tasks.length - 1].id;
} else {
const previousProject =
filteredProjects.value[currentHighlightedIndex - 1];
if (previousProject.tasks.length > 0) {
// highlight last task of previous project
highlightedItemId.value =
previousProject.tasks[previousProject.tasks.length - 1].id;
} else {
highlightedItemId.value =
filteredProjects.value[
currentHighlightedIndex - 1
].project.id;
}
highlightedItemId.value =
filteredProjects.value[
filteredProjects.value.length - 1
].project.id;
}
} else {
const previousProject =
filteredProjects.value[currentHighlightedIndex - 1];
if (previousProject.tasks.length > 0) {
// highlight last task of previous project
highlightedItemId.value =
previousProject.tasks[previousProject.tasks.length - 1].id;
} else {
highlightedItemId.value =
filteredProjects.value[currentHighlightedIndex - 1].project.id;
}
}
}
function moveHighlightDown() {
if (highlightedItemId.value) {
const currentHighlightedIndex = filteredProjects.value.findIndex(
const currentHighlightedIndex = filteredProjects.value.findIndex(
(projectWithTasks) =>
projectWithTasks.project.id === highlightedItemId.value
);
// check if it is a project id
if (currentHighlightedIndex === -1) {
// the ID is a task ID
const currentProjectWithTasks = filteredProjects.value.find(
(projectWithTasks) =>
projectWithTasks.project.id === highlightedItemId.value
);
// check if it is a project id
if (currentHighlightedIndex === -1) {
// the ID is a task ID
const currentProjectWithTasks = filteredProjects.value.find(
(projectWithTasks) =>
projectWithTasks.tasks.some(
(task) => task.id === highlightedItemId.value
)
);
if (currentProjectWithTasks) {
const taskIndex = currentProjectWithTasks.tasks.findIndex(
projectWithTasks.tasks.some(
(task) => task.id === highlightedItemId.value
)
);
if (currentProjectWithTasks) {
const taskIndex = currentProjectWithTasks.tasks.findIndex(
(task) => task.id === highlightedItemId.value
);
if (taskIndex === -1) {
return;
}
if (taskIndex === currentProjectWithTasks.tasks.length - 1) {
// highlight the next project if it was the last task in current project
const projectIndex = filteredProjects.value.indexOf(
currentProjectWithTasks
);
if (taskIndex === -1) {
return;
if (projectIndex === filteredProjects.value.length - 1) {
// highlight the first project if it was the last project
highlightedItemId.value =
filteredProjects.value[0].project.id;
} else {
highlightedItemId.value =
filteredProjects.value[projectIndex + 1].project.id;
}
if (taskIndex === currentProjectWithTasks.tasks.length - 1) {
// highlight the next project if it was the last task in current project
const projectIndex = filteredProjects.value.indexOf(
currentProjectWithTasks
);
if (projectIndex === filteredProjects.value.length - 1) {
// highlight the first project if it was the last project
highlightedItemId.value =
filteredProjects.value[0].project.id;
} else {
highlightedItemId.value =
filteredProjects.value[projectIndex + 1].project.id;
}
return;
}
highlightedItemId.value =
currentProjectWithTasks.tasks[taskIndex + 1].id;
return;
}
highlightedItemId.value =
currentProjectWithTasks.tasks[taskIndex + 1].id;
}
if (currentHighlightedIndex === filteredProjects.value.length - 1) {
// highlight the first project or the last project of the last project
const lastProject =
filteredProjects.value[filteredProjects.value.length - 1];
if (lastProject.tasks.length > 0) {
// highlight last task of last project
highlightedItemId.value = lastProject.tasks[0].id;
} else {
highlightedItemId.value = filteredProjects.value[0].project.id;
}
}
if (currentHighlightedIndex === filteredProjects.value.length - 1) {
// highlight the first project or the last project of the last project
const lastProject =
filteredProjects.value[filteredProjects.value.length - 1];
if (lastProject.tasks.length > 0) {
// highlight last task of last project
highlightedItemId.value = lastProject.tasks[0].id;
} else {
const currentProjectWithTasks =
filteredProjects.value[currentHighlightedIndex];
if (currentProjectWithTasks.tasks.length > 0) {
// highlight last task of previous project
highlightedItemId.value = currentProjectWithTasks.tasks[0].id;
} else {
highlightedItemId.value =
filteredProjects.value[
currentHighlightedIndex + 1
].project.id;
}
highlightedItemId.value = filteredProjects.value[0].project.id;
}
} else {
const currentProjectWithTasks =
filteredProjects.value[currentHighlightedIndex];
if (currentProjectWithTasks.tasks.length > 0) {
// highlight last task of previous project
highlightedItemId.value = currentProjectWithTasks.tasks[0].id;
} else {
highlightedItemId.value =
filteredProjects.value[currentHighlightedIndex + 1].project.id;
}
}
}
@@ -307,10 +299,23 @@ function selectProject(projectId: string) {
open.value = false;
emit('changed', project.value, task.value);
}
const showCreateProject = ref(false);
</script>
<template>
<Dropdown width="120" v-model="open" :closeOnContentClick="true">
<div v-if="projects.length === 0">
<Badge
@click="showCreateProject = true"
size="large"
class="cursor-pointer hover:bg-tertiary">
<PlusIcon class="-ml-1 w-5"></PlusIcon>
<span>Add new project</span>
</Badge>
<ProjectCreateModal
v-model:show="showCreateProject"></ProjectCreateModal>
</div>
<Dropdown v-else width="120" v-model="open" :closeOnContentClick="true">
<template #trigger>
<ProjectBadge
ref="projectDropdownTrigger"
@@ -319,7 +324,7 @@ function selectProject(projectId: string) {
:border="showBadgeBorder"
tag="button"
:name="selectedProjectName"
class="focus:border-input-border-active focus:outline-0 focus:bg-card-background-separator hover:bg-card-background-separator">
class="focus:border-border-tertiary focus:outline-0 focus:bg-card-background-separator hover:bg-card-background-separator">
<div class="flex items-center space-x-1">
<span>
{{ selectedProjectName }}

View File

@@ -25,7 +25,7 @@ const iconColorClasses = computed(() => {
:class="
twMerge(
iconColorClasses,
'flex-shrink-0 ring-0 focus:outline-none focus:ring-0 transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full w-7 sm:w-10 h-7 sm:h-10 flex items-center justify-center'
'flex-shrink-0 ring-0 focus:outline-none focus:ring-0 transition focus-visible:bg-card-background-separator hover:bg-card-background-separator rounded-full w-7 sm:w-10 h-7 sm:h-10 flex items-center justify-center'
)
">
<TagIcon class="w-5 sm:w-6 h-5 sm:h-6"></TagIcon>

View File

@@ -27,7 +27,7 @@ const buttonColorClasses = computed(() => {
if (props.active) {
return 'bg-red-400/80 hover:bg-red-500/80 focus:bg-red-500/80';
} else {
return 'bg-accent-300/50 hover:bg-accent-400/70 focus:bg-accent-400/70';
return 'bg-accent-300/70 hover:bg-accent-400/70 focus:bg-accent-400/70';
}
});