mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 03:32:15 +01:00
Conditionally show cost column in report tables; Task/Project Modal
Field cleanup; improve estimated time UX
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import ProjectBadge from '@/packages/ui/src/Project/ProjectBadge.vue';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useProjectsQuery } from '@/utils/useProjectsQuery';
|
||||
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
|
||||
@@ -11,13 +10,16 @@ import {
|
||||
ComboboxRoot,
|
||||
ComboboxViewport,
|
||||
} from 'radix-vue';
|
||||
import { PlusCircleIcon } from '@heroicons/vue/20/solid';
|
||||
import { api } from '@/packages/api/src';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { getRandomColor } from '@/packages/ui/src/utils/color';
|
||||
import type { Project } from '@/packages/api/src';
|
||||
import ProjectDropdownItem from '@/packages/ui/src/Project/ProjectDropdownItem.vue';
|
||||
import { Check, Plus } from 'lucide-vue-next';
|
||||
import type { CreateClientBody, CreateProjectBody, Project } from '@/packages/api/src';
|
||||
import { UseFocusTrap } from '@vueuse/integrations/useFocusTrap/component';
|
||||
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
||||
import { useProjectsStore } from '@/utils/useProjects';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import { useClientsQuery } from '@/utils/useClientsQuery';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
import { canCreateProjects } from '@/utils/permissions';
|
||||
|
||||
const searchValue = ref('');
|
||||
const searchInput = ref<HTMLElement | null>(null);
|
||||
@@ -25,10 +27,13 @@ const model = defineModel<string | null>({
|
||||
default: null,
|
||||
});
|
||||
const open = ref(false);
|
||||
const { projects, invalidateProjects } = useProjectsQuery();
|
||||
const showCreateProject = ref(false);
|
||||
const { projects } = useProjectsQuery();
|
||||
const { clients } = useClientsQuery();
|
||||
const emit = defineEmits(['update:modelValue', 'changed']);
|
||||
|
||||
const projectDropdownTrigger = ref<HTMLElement | null>(null);
|
||||
const activeClients = computed(() => clients.value.filter((c) => !c.is_archived));
|
||||
|
||||
const sortedProjects = ref<Project[]>([]);
|
||||
|
||||
const shownProjects = computed(() => {
|
||||
@@ -37,38 +42,17 @@ const shownProjects = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
border?: boolean;
|
||||
}>(),
|
||||
{
|
||||
border: true,
|
||||
async function handleCreateProject(projectBody: CreateProjectBody) {
|
||||
const newProject = await useProjectsStore().createProject(projectBody);
|
||||
if (newProject) {
|
||||
model.value = newProject.id;
|
||||
emit('changed');
|
||||
}
|
||||
);
|
||||
return newProject;
|
||||
}
|
||||
|
||||
const page = usePage<{
|
||||
auth: {
|
||||
user: {
|
||||
current_team_id: string;
|
||||
};
|
||||
};
|
||||
}>();
|
||||
|
||||
async function addProjectIfNoneExists() {
|
||||
if (searchValue.value.length > 0 && shownProjects.value.length === 0) {
|
||||
const response = await api.createProject(
|
||||
{
|
||||
name: searchValue.value,
|
||||
color: getRandomColor(),
|
||||
is_billable: false,
|
||||
},
|
||||
{ params: { organization: page.props.auth.user.current_team_id } }
|
||||
);
|
||||
invalidateProjects();
|
||||
model.value = response.data.id;
|
||||
searchValue.value = '';
|
||||
open.value = false;
|
||||
}
|
||||
async function handleCreateClient(clientBody: CreateClientBody) {
|
||||
return await useClientsStore().createClient(clientBody);
|
||||
}
|
||||
|
||||
watch(open, (isOpen) => {
|
||||
@@ -107,16 +91,12 @@ function updateValue(project: Project) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dropdown v-model="open" align="start" width="60">
|
||||
<Dropdown v-model="open" align="start">
|
||||
<template #trigger>
|
||||
<ProjectBadge
|
||||
ref="projectDropdownTrigger"
|
||||
:color="selectedProjectColor"
|
||||
size="xlarge"
|
||||
:border
|
||||
tag="button"
|
||||
:name="selectedProjectName"
|
||||
class="focus:border-input-border-active bg-input-background focus:outline-0 focus:bg-card-background-separator hover:bg-card-background-separator"></ProjectBadge>
|
||||
<slot
|
||||
name="trigger"
|
||||
:selected-project-name="selectedProjectName"
|
||||
:selected-project-color="selectedProjectColor"></slot>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
@@ -130,40 +110,53 @@ function updateValue(project: Project) {
|
||||
<ComboboxAnchor>
|
||||
<ComboboxInput
|
||||
ref="searchInput"
|
||||
class="bg-card-background border-0 placeholder-text-tertiary text-sm text-text-primary py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
|
||||
placeholder="Search for a project..."
|
||||
@keydown.enter="addProjectIfNoneExists" />
|
||||
class="bg-transparent border-0 placeholder-muted-foreground text-sm text-popover-foreground py-2 px-3 focus:ring-0 border-b border-popover-border focus:border-popover-border w-full"
|
||||
placeholder="Search for a project..." />
|
||||
</ComboboxAnchor>
|
||||
<ComboboxContent>
|
||||
<ComboboxViewport
|
||||
ref="dropdownViewport"
|
||||
class="w-60 max-h-60 overflow-y-scroll">
|
||||
class="w-[--reka-popper-anchor-width] max-h-60 overflow-y-scroll p-1">
|
||||
<ComboboxItem
|
||||
v-for="project in shownProjects"
|
||||
:key="project.id"
|
||||
:value="project"
|
||||
class="data-[highlighted]:bg-card-background-active"
|
||||
class="relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
|
||||
:data-project-id="project.id">
|
||||
<ProjectDropdownItem
|
||||
:selected="isProjectSelected(project)"
|
||||
:color="project.color"
|
||||
:name="project.name"></ProjectDropdownItem>
|
||||
<span class="flex items-center gap-2">
|
||||
<span
|
||||
:style="{ backgroundColor: project.color }"
|
||||
class="w-3 h-3 rounded-full shrink-0"></span>
|
||||
<span>{{ project.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="isProjectSelected(project)"
|
||||
class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<Check class="h-4 w-4" />
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
<div
|
||||
v-if="searchValue.length > 0 && shownProjects.length === 0"
|
||||
class="bg-card-background-active">
|
||||
<div
|
||||
class="flex space-x-3 items-center px-4 py-3 text-xs font-medium border-t rounded-b-lg border-card-background-separator">
|
||||
<PlusCircleIcon class="w-5 flex-shrink-0"></PlusCircleIcon>
|
||||
<span>Add "{{ searchValue }}" as a new Project</span>
|
||||
</div>
|
||||
</div>
|
||||
</ComboboxViewport>
|
||||
<div
|
||||
v-if="canCreateProjects()"
|
||||
class="flex items-center gap-2 px-3 py-2 text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground border-t border-popover-border"
|
||||
@click="
|
||||
open = false;
|
||||
showCreateProject = true;
|
||||
">
|
||||
<Plus class="h-4 w-4 shrink-0" />
|
||||
<span>Create new Project</span>
|
||||
</div>
|
||||
</ComboboxContent>
|
||||
</ComboboxRoot>
|
||||
</UseFocusTrap>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<ProjectCreateModal
|
||||
v-model:show="showCreateProject"
|
||||
:create-project="handleCreateProject"
|
||||
:create-client="handleCreateClient"
|
||||
:clients="activeClients"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:enable-estimated-time="isAllowedToPerformPremiumAction()" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -9,12 +9,13 @@ import { useProjectsStore } from '@/utils/useProjects';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import ClientDropdown from '@/packages/ui/src/Client/ClientDropdown.vue';
|
||||
import Badge from '@/packages/ui/src/Badge.vue';
|
||||
import { useClientsQuery } from '@/utils/useClientsQuery';
|
||||
import ProjectColorSelector from '@/packages/ui/src/Project/ProjectColorSelector.vue';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import { UserCircleIcon } from '@heroicons/vue/20/solid';
|
||||
import EstimatedTimeSection from '@/packages/ui/src/EstimatedTimeSection.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import { Field, FieldGroup, FieldLabel } from '@/packages/ui/src/field';
|
||||
import ProjectBillableRateModal from '@/packages/ui/src/Project/ProjectBillableRateModal.vue';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import ProjectEditBillableSection from '@/packages/ui/src/Project/ProjectEditBillableSection.vue';
|
||||
@@ -81,59 +82,47 @@ async function submitBillableRate() {
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="sm:flex items-center space-y-2 sm:space-y-0 sm:space-x-5">
|
||||
<Field class="flex-1 flex items-center">
|
||||
<div class="text-center">
|
||||
<FieldGroup>
|
||||
<FieldGroup class="flex-row items-end">
|
||||
<Field class="w-auto text-center">
|
||||
<FieldLabel for="color">Color</FieldLabel>
|
||||
<ProjectColorSelector v-model="project.color"></ProjectColorSelector>
|
||||
</div>
|
||||
</Field>
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="projectName">Project name</FieldLabel>
|
||||
<TextInput
|
||||
id="projectName"
|
||||
ref="projectNameInput"
|
||||
v-model="project.name"
|
||||
type="text"
|
||||
placeholder="Project Name"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="projectName"
|
||||
@keydown.enter="submit()" />
|
||||
</Field>
|
||||
</Field>
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="projectName">Project name</FieldLabel>
|
||||
<TextInput
|
||||
id="projectName"
|
||||
ref="projectNameInput"
|
||||
v-model="project.name"
|
||||
type="text"
|
||||
placeholder="Project Name"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="projectName"
|
||||
@keydown.enter="submit()" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="client">Client</FieldLabel>
|
||||
<FieldLabel for="client" :icon="UserCircleIcon">Client</FieldLabel>
|
||||
<ClientDropdown v-model="project.client_id" :create-client :clients="clients">
|
||||
<template #trigger>
|
||||
<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 class="whitespace-nowrap">
|
||||
{{ currentClientName }}
|
||||
</span>
|
||||
</div>
|
||||
</Badge>
|
||||
<Button variant="input" class="w-full justify-between">
|
||||
<span class="truncate">{{ currentClientName }}</span>
|
||||
<ChevronDown class="w-4 h-4 text-icon-default" />
|
||||
</Button>
|
||||
</template>
|
||||
</ClientDropdown>
|
||||
</Field>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<ProjectEditBillableSection
|
||||
v-model:is-billable="project.is_billable"
|
||||
v-model:billable-rate="project.billable_rate"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
@submit="submit"></ProjectEditBillableSection>
|
||||
</div>
|
||||
<div>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="project.estimated_time"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</div>
|
||||
</div>
|
||||
<ProjectEditBillableSection
|
||||
v-model:is-billable="project.is_billable"
|
||||
v-model:billable-rate="project.billable_rate"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
@submit="submit"></ProjectEditBillableSection>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="project.estimated_time"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>
|
||||
|
||||
@@ -292,7 +292,9 @@ const tableData = computed(() => {
|
||||
<div
|
||||
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-secondary [&>*]:pb-1.5 [&>*]:pt-1 text-text-tertiary text-sm">
|
||||
<div class="pl-6">Name</div>
|
||||
<div class="text-right">Duration</div>
|
||||
<div class="text-right" :class="!showBillableRate ? 'pr-6' : ''">
|
||||
Duration
|
||||
</div>
|
||||
<div v-if="showBillableRate" class="text-right pr-6">Cost</div>
|
||||
</div>
|
||||
<template
|
||||
@@ -311,7 +313,9 @@ const tableData = computed(() => {
|
||||
<div class="flex items-center pl-6 font-medium">
|
||||
<span>Total</span>
|
||||
</div>
|
||||
<div class="justify-end flex items-center font-medium">
|
||||
<div
|
||||
class="justify-end flex items-center font-medium"
|
||||
:class="!showBillableRate ? 'pr-6' : ''">
|
||||
{{
|
||||
formatHumanReadableDuration(
|
||||
aggregatedTableTimeEntries.seconds,
|
||||
@@ -324,7 +328,8 @@ const tableData = computed(() => {
|
||||
v-if="showBillableRate"
|
||||
class="justify-end pr-6 flex items-center font-medium">
|
||||
{{
|
||||
aggregatedTableTimeEntries.cost
|
||||
aggregatedTableTimeEntries.cost !== null &&
|
||||
aggregatedTableTimeEntries.cost !== undefined
|
||||
? formatCents(
|
||||
aggregatedTableTimeEntries.cost,
|
||||
getOrganizationCurrencyString(),
|
||||
|
||||
@@ -42,7 +42,7 @@ const organization = inject<ComputedRef<Organization>>('organization');
|
||||
{{ entry.description }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="justify-end flex items-center">
|
||||
<div class="justify-end flex items-center" :class="!showCost ? 'pr-6' : ''">
|
||||
{{
|
||||
formatHumanReadableDuration(
|
||||
entry.seconds,
|
||||
|
||||
@@ -9,6 +9,10 @@ import { useTasksStore } from '@/utils/useTasks';
|
||||
import ProjectDropdown from '@/Components/Common/Project/ProjectDropdown.vue';
|
||||
import EstimatedTimeSection from '@/packages/ui/src/EstimatedTimeSection.vue';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
import { Field, FieldGroup, FieldLabel } from '@/packages/ui/src/field';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import { FolderIcon } from '@heroicons/vue/20/solid';
|
||||
|
||||
const { createTask } = useTasksStore();
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -54,8 +58,9 @@ useFocus(taskNameInput, { initialValue: true });
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1">
|
||||
<FieldGroup>
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="taskName">Task name</FieldLabel>
|
||||
<TextInput
|
||||
id="taskName"
|
||||
ref="taskNameInput"
|
||||
@@ -66,15 +71,28 @@ useFocus(taskNameInput, { initialValue: true });
|
||||
required
|
||||
autocomplete="taskName"
|
||||
@keydown.enter="submit()" />
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<ProjectDropdown v-model="taskProjectId"></ProjectDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="estimatedTime"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</Field>
|
||||
<Field class="w-auto">
|
||||
<FieldLabel :icon="FolderIcon" for="project">Project</FieldLabel>
|
||||
<ProjectDropdown v-model="taskProjectId">
|
||||
<template #trigger="{ selectedProjectName, selectedProjectColor }">
|
||||
<Button variant="input" class="w-full justify-between">
|
||||
<span class="flex items-center gap-2 truncate">
|
||||
<span
|
||||
:style="{ backgroundColor: selectedProjectColor }"
|
||||
class="w-3 h-3 rounded-full shrink-0"></span>
|
||||
<span class="truncate">{{ selectedProjectName }}</span>
|
||||
</span>
|
||||
<ChevronDown class="w-4 h-4 text-icon-default" />
|
||||
</Button>
|
||||
</template>
|
||||
</ProjectDropdown>
|
||||
</Field>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="estimatedTime"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useTasksStore } from '@/utils/useTasks';
|
||||
import type { Task, UpdateTaskBody } from '@/packages/api/src';
|
||||
import EstimatedTimeSection from '@/packages/ui/src/EstimatedTimeSection.vue';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
import { Field, FieldGroup, FieldLabel } from '@/packages/ui/src/field';
|
||||
|
||||
const { updateTask } = useTasksStore();
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -42,24 +43,25 @@ useFocus(taskNameInput, { initialValue: true });
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1">
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="taskName">Task name</FieldLabel>
|
||||
<TextInput
|
||||
id="taskName"
|
||||
ref="taskNameInput"
|
||||
v-model="taskBody.name"
|
||||
type="text"
|
||||
placeholder="Task Name"
|
||||
class="mt-1 block w-full"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="taskName"
|
||||
@keydown.enter="submit()" />
|
||||
</div>
|
||||
</div>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="taskBody.estimated_time"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</Field>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-model="taskBody.estimated_time"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>
|
||||
|
||||
@@ -114,6 +114,12 @@ const tableData = computed(() => {
|
||||
}) ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const showBillableRate = computed(() => {
|
||||
return !!(
|
||||
getCurrentRole() !== 'employee' || organization?.value?.employees_can_see_billable_rates
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -132,15 +138,20 @@ const tableData = computed(() => {
|
||||
"></ReportingGroupBySelect>
|
||||
</div>
|
||||
|
||||
<div class="grid items-center" style="grid-template-columns: 1fr 100px 150px">
|
||||
<div
|
||||
class="grid items-center"
|
||||
:style="`grid-template-columns: 1fr 100px ${showBillableRate ? '150px' : ''}`">
|
||||
<div
|
||||
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:pb-1.5 [&>*]:pt-1 text-text-tertiary text-sm">
|
||||
<div class="pl-6">Name</div>
|
||||
<div class="text-right">Duration</div>
|
||||
<div class="text-right pr-6">Cost</div>
|
||||
<div class="text-right" :class="!showBillableRate ? 'pr-6' : ''">Duration</div>
|
||||
<div v-if="showBillableRate" class="text-right pr-6">Cost</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="flex justify-center py-10 col-span-3 text-text-tertiary">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="flex justify-center py-10 text-text-tertiary"
|
||||
:class="showBillableRate ? 'col-span-3' : 'col-span-2'">
|
||||
Loading reporting data…
|
||||
</div>
|
||||
|
||||
@@ -153,12 +164,15 @@ const tableData = computed(() => {
|
||||
v-for="entry in tableData"
|
||||
:key="entry.description ?? 'none'"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:show-cost="showBillableRate"
|
||||
:entry="entry"></ReportingRow>
|
||||
<div class="contents [&>*]:transition text-text-tertiary [&>*]:h-[50px]">
|
||||
<div class="flex items-center pl-6 font-medium">
|
||||
<span>Total</span>
|
||||
</div>
|
||||
<div class="justify-end flex items-center font-medium">
|
||||
<div
|
||||
class="justify-end flex items-center font-medium"
|
||||
:class="!showBillableRate ? 'pr-6' : ''">
|
||||
{{
|
||||
formatHumanReadableDuration(
|
||||
aggregatedTableTimeEntries.seconds,
|
||||
@@ -167,7 +181,9 @@ const tableData = computed(() => {
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="justify-end pr-6 flex items-center font-medium">
|
||||
<div
|
||||
v-if="showBillableRate"
|
||||
class="justify-end pr-6 flex items-center font-medium">
|
||||
{{
|
||||
aggregatedTableTimeEntries.cost
|
||||
? formatCents(
|
||||
@@ -183,7 +199,10 @@ const tableData = computed(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="chart flex flex-col items-center justify-center py-12 col-span-3">
|
||||
<div
|
||||
v-else
|
||||
class="chart flex flex-col items-center justify-center py-12"
|
||||
:class="showBillableRate ? 'col-span-3' : 'col-span-2'">
|
||||
<p class="text-lg text-text-primary font-medium">No time entries found</p>
|
||||
<p>Try to track some time entries this week</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user