mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 12:42:15 +01:00
change color palette, change user_id to member_id
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
37
resources/js/Components/Common/GroupedItemsCountButton.vue
Normal file
37
resources/js/Components/Common/GroupedItemsCountButton.vue
Normal 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>
|
||||
14
resources/js/Components/Common/Icons/BillableIcon.vue
Normal file
14
resources/js/Components/Common/Icons/BillableIcon.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -29,7 +29,7 @@ async function invitePlaceholder(id: string) {
|
||||
{
|
||||
params: {
|
||||
organization: organizationId,
|
||||
membership: id,
|
||||
member: id,
|
||||
},
|
||||
}
|
||||
),
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
formatHumanReadableDuration,
|
||||
getDayJsInstance,
|
||||
} from '@/utils/time';
|
||||
import { useCssVar } from '@vueuse/core';
|
||||
|
||||
const props = defineProps<{
|
||||
dailyHoursTracked: { duration: number; date: string }[];
|
||||
@@ -40,6 +41,8 @@ const max = Math.max(
|
||||
1
|
||||
);
|
||||
|
||||
const backgroundColor = useCssVar('--color-bg-secondary');
|
||||
const itemBackgroundColor = useCssVar('--color-bg-tertiary');
|
||||
const option = ref({
|
||||
tooltip: {},
|
||||
visualMap: {
|
||||
@@ -50,7 +53,7 @@ const option = ref({
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
inRange: {
|
||||
color: ['#242940', '#2DBE45'],
|
||||
color: [itemBackgroundColor.value, '#2DBE45'],
|
||||
},
|
||||
show: false,
|
||||
},
|
||||
@@ -74,8 +77,9 @@ const option = ref({
|
||||
.format('YYYY-MM-DD'),
|
||||
],
|
||||
itemStyle: {
|
||||
color: 'transparent',
|
||||
borderWidth: 8,
|
||||
borderColor: '#13152B',
|
||||
borderColor: backgroundColor.value,
|
||||
},
|
||||
yearLabel: { show: false },
|
||||
},
|
||||
@@ -85,6 +89,8 @@ const option = ref({
|
||||
data: props.dailyHoursTracked.map((el) => [el.date, el.duration]),
|
||||
itemStyle: {
|
||||
borderRadius: 5,
|
||||
borderColor: 'rgba(255,255,255,0.05)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
tooltip: {
|
||||
valueFormatter: (value: number, dataIndex: number) => {
|
||||
@@ -104,10 +110,9 @@ const option = ref({
|
||||
<DashboardCard title="Activity Graph" :icon="BoltIcon">
|
||||
<div class="px-2">
|
||||
<v-chart
|
||||
:autoresize="true"
|
||||
class="chart"
|
||||
:option="option"
|
||||
style="height: 260px" />
|
||||
style="height: 260px; background-color: transparent" />
|
||||
</div>
|
||||
</DashboardCard>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<section class="flex flex-col">
|
||||
<CardTitle :title="title" :icon="icon"></CardTitle>
|
||||
<div
|
||||
class="rounded-lg bg-card-background border border-card-border flex-1 flex items-stretch">
|
||||
class="rounded-lg bg-card-background border border-card-border flex-1 flex items-stretch shadow-card">
|
||||
<div class="w-full flex flex-col">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import VChart from 'vue-echarts';
|
||||
import { ref } from 'vue';
|
||||
import { useCssVar } from '@vueuse/core';
|
||||
|
||||
const props = defineProps<{
|
||||
history: number[];
|
||||
}>();
|
||||
|
||||
const accentColor = useCssVar('--color-accent-quaternary');
|
||||
|
||||
const seriesData = props.history.map((el) => {
|
||||
return {
|
||||
value: el,
|
||||
...{
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(125,156,188,1)',
|
||||
borderColor: 'rgba(' + accentColor.value + ',0.8)',
|
||||
borderRadius: [2, 2, 0, 0],
|
||||
color: 'rgba(125,156,188,1)',
|
||||
color: 'rgba(' + accentColor.value + ',0.8)',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
} from 'echarts/components';
|
||||
import { useCssVar } from '@vueuse/core';
|
||||
import { formatHumanReadableDuration } from '@/utils/time';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
@@ -23,6 +25,8 @@ use([
|
||||
|
||||
provide(THEME_KEY, 'dark');
|
||||
|
||||
const backgroundColor = useCssVar('--theme-color-default-background');
|
||||
|
||||
function hexToRGBA(hex: string, opacity = 1) {
|
||||
// Remove the hash at the start if it's there
|
||||
hex = hex.replace(/^#/, '');
|
||||
@@ -60,7 +64,7 @@ const seriesData = props.weeklyProjectOverview.map((el) => {
|
||||
itemStyle: {
|
||||
borderRadius: 15,
|
||||
// TODO: Fix dynamic color
|
||||
borderColor: '#0b0d1c',
|
||||
borderColor: backgroundColor.value,
|
||||
borderWidth: 18,
|
||||
color: new LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
@@ -77,13 +81,23 @@ const seriesData = props.weeklyProjectOverview.map((el) => {
|
||||
};
|
||||
});
|
||||
const option = ref({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
bottom: 'bottom',
|
||||
},
|
||||
backgroundColor: 'transparent',
|
||||
series: [
|
||||
{
|
||||
label: {
|
||||
// TODO: Muted color make dynamic
|
||||
color: '#D9DCFB',
|
||||
fontWeight: 'bold',
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
valueFormatter: (value: number) => {
|
||||
return formatHumanReadableDuration(value);
|
||||
},
|
||||
},
|
||||
data: seriesData,
|
||||
radius: ['30%', '65%'],
|
||||
|
||||
@@ -18,6 +18,7 @@ import ProjectsChartCard from '@/Components/Dashboard/ProjectsChartCard.vue';
|
||||
import { formatHumanReadableDuration } from '@/utils/time';
|
||||
import { formatCents } from '@/utils/money';
|
||||
import { getWeekStart } from '@/utils/useUser';
|
||||
import { useCssVar } from '@vueuse/core';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
@@ -47,6 +48,7 @@ const props = defineProps<{
|
||||
duration: number;
|
||||
}[];
|
||||
}>();
|
||||
const accentColor = useCssVar('--color-accent-quaternary');
|
||||
|
||||
const seriesData = props.weeklyHistory.map((el) => {
|
||||
return {
|
||||
@@ -56,23 +58,34 @@ const seriesData = props.weeklyHistory.map((el) => {
|
||||
borderColor: new LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(125,156,188,1)',
|
||||
color: 'rgba(' + accentColor.value + ',0.7)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(125,156,188,0.7)',
|
||||
color: 'rgba(' + accentColor.value + ',0.5)',
|
||||
},
|
||||
]),
|
||||
borderWidth: 3,
|
||||
emphasis: {
|
||||
color: new LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(' + accentColor.value + ',0.9)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(' + accentColor.value + ',0.7)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
borderRadius: [12, 12, 0, 0],
|
||||
color: new LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(125,156,188,0.9)',
|
||||
color: 'rgba(' + accentColor.value + ',0.7)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(125,156,188,0.4)',
|
||||
color: 'rgba(' + accentColor.value + ',0.5)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
@@ -106,6 +119,8 @@ const weekdays = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const markLineColor = useCssVar('--color-border-secondary');
|
||||
|
||||
const option = ref({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
@@ -120,12 +135,6 @@ const option = ref({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: weekdays.value,
|
||||
markLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(125,156,188,0.1)',
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'transparent', // Set desired color here
|
||||
@@ -147,7 +156,7 @@ const option = ref({
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(125,156,188,0.2)', // Set desired color here
|
||||
color: markLineColor.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ const close = () => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row justify-end px-6 py-4 border-t border-card-background-separator bg-card-background text-end">
|
||||
class="flex flex-row justify-end px-6 py-4 border-t border-card-background-separator bg-default-background rounded-b-2xl text-end">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { flip, type Placement, useFloating } from '@floating-ui/vue';
|
||||
import { offset } from '@floating-ui/vue';
|
||||
import { autoUpdate } from '@floating-ui/vue';
|
||||
@@ -7,12 +7,10 @@ import { autoUpdate } from '@floating-ui/vue';
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
align: Placement;
|
||||
width: string;
|
||||
closeOnContentClick: boolean;
|
||||
}>(),
|
||||
{
|
||||
align: 'bottom-start',
|
||||
width: '48',
|
||||
closeOnContentClick: true,
|
||||
}
|
||||
);
|
||||
@@ -39,12 +37,6 @@ function onContentClick() {
|
||||
}
|
||||
}
|
||||
|
||||
const widthClass = computed(() => {
|
||||
return {
|
||||
48: 'w-48',
|
||||
}[props.width.toString()];
|
||||
});
|
||||
|
||||
function toggleOpen() {
|
||||
open.value = !open.value;
|
||||
if (open.value === true) {
|
||||
@@ -82,7 +74,6 @@ const { floatingStyles } = useFloating(reference, floating, {
|
||||
v-show="open"
|
||||
ref="floating"
|
||||
class="z-50"
|
||||
:class="[widthClass]"
|
||||
:style="floatingStyles"
|
||||
@click="onContentClick">
|
||||
<transition
|
||||
@@ -94,7 +85,7 @@ const { floatingStyles } = useFloating(reference, floating, {
|
||||
leave-to-class="transform opacity-0 scale-95">
|
||||
<div
|
||||
v-if="open"
|
||||
class="rounded-lg ring-1 relative ring-black ring-opacity-5 border border-card-border overflow-none bg-card-background shadow-lg">
|
||||
class="rounded-lg ring-1 relative ring-black ring-opacity-5 border border-card-border overflow-none shadow-dropdown bg-card-background">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
@@ -91,7 +91,7 @@ const maxWidthClass = computed(() => {
|
||||
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||
<div
|
||||
v-show="show"
|
||||
class="mb-6 bg-card-background border border-card-border rounded-lg shadow-xl transform transition-all sm:w-full sm:mx-auto"
|
||||
class="mb-6 bg-default-background border border-card-border rounded-lg shadow-xl transform transition-all sm:w-full sm:mx-auto"
|
||||
:class="maxWidthClass">
|
||||
<slot v-if="show" />
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ const sizeClasses = {
|
||||
:type="type"
|
||||
:class="
|
||||
twMerge(
|
||||
'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 ease-in-out',
|
||||
'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus-visible:border-input-border-active focus:outline-none focus:ring-0 disabled:opacity-25 ease-in-out',
|
||||
sizeClasses[props.size]
|
||||
)
|
||||
">
|
||||
|
||||
@@ -28,7 +28,7 @@ function updateValue(event: Event) {
|
||||
<template>
|
||||
<input
|
||||
ref="input"
|
||||
class="border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm"
|
||||
class="border-input-border border bg-input-background text-white focus:ring-input-border-active focus:ring-0 focus-visible:border-input-border-active rounded-md shadow-sm"
|
||||
:value="modelValue"
|
||||
:name="name"
|
||||
@input="updateValue" />
|
||||
|
||||
@@ -186,7 +186,7 @@ function switchToTimeEntryOrganization() {
|
||||
</div>
|
||||
<div class="flex items-center relative" data-testid="dashboard_timer">
|
||||
<div
|
||||
class="flex flex-col sm:flex-row w-full rounded-lg bg-card-background border-card-border border transition">
|
||||
class="flex flex-col sm:flex-row w-full rounded-lg bg-card-background border-card-border border transition shadow-card">
|
||||
<div class="flex-1 flex items-center pr-6">
|
||||
<input
|
||||
placeholder="What are you working on?"
|
||||
@@ -194,7 +194,7 @@ function switchToTimeEntryOrganization() {
|
||||
v-model="currentTimeEntry.description"
|
||||
@keydown.enter="startTimerIfNotActive"
|
||||
@blur="updateTimeEntry"
|
||||
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3 border-b border-b-card-background-separator sm:px-4 text-base sm:text-lg text-white focus:bg-card-background-active font-medium bg-transparent border-none placeholder-muted focus:ring-0 transition"
|
||||
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3 border-b border-b-card-background-separator sm:px-4 text-base sm:text-lg text-white font-medium bg-transparent border-none placeholder-muted focus:ring-0 transition"
|
||||
type="text" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between pl-2">
|
||||
@@ -226,7 +226,7 @@ function switchToTimeEntryOrganization() {
|
||||
@blur="updateTimerAndStartLiveTimerUpdate"
|
||||
@keydown.enter="onTimeEntryEnterPress"
|
||||
v-model="currentTime"
|
||||
class="w-[110px] sm:w-[130px] h-full text-white py-2.5 rounded-r-lg text-center px-4 text-sm sm:text-lg font-bold bg-card-background border-none placeholder-muted focus:ring-0 transition focus:bg-card-background-active"
|
||||
class="w-[110px] sm:w-[130px] h-full text-white py-2.5 rounded-r-lg text-center px-4 text-sm sm:text-lg font-bold bg-card-background border-none placeholder-muted focus:ring-0 transition"
|
||||
type="text" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user