mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 03:02:15 +01:00
Conditionally show cost column in report tables; Task/Project Modal
Field cleanup; improve estimated time UX
This commit is contained in:
@@ -10,12 +10,12 @@ const model = defineModel<string>({ default: '' });
|
||||
<Dropdown align="center">
|
||||
<template #trigger>
|
||||
<button
|
||||
class="p-2 bg-input-background hover:bg-tertiary transition rounded-full border border-input-border">
|
||||
class="h-9 w-9 flex items-center justify-center bg-input-background hover:bg-tertiary transition rounded-full border border-input-border">
|
||||
<div
|
||||
:style="{
|
||||
backgroundColor: model,
|
||||
}"
|
||||
class="w-6 h-6 rounded-full cursor-pointer"></div>
|
||||
class="w-5 h-5 rounded-full cursor-pointer"></div>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
|
||||
@@ -8,11 +8,12 @@ import { getRandomColor } from '@/packages/ui/src/utils/color';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import ClientDropdown from '@/packages/ui/src/Client/ClientDropdown.vue';
|
||||
import Badge from '@/packages/ui/src/Badge.vue';
|
||||
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 '../field';
|
||||
import { Field, FieldGroup, FieldLabel } from '../field';
|
||||
import ProjectEditBillableSection from '@/packages/ui/src/Project/ProjectEditBillableSection.vue';
|
||||
import type { Client } from '@/packages/api/src';
|
||||
|
||||
@@ -74,9 +75,9 @@ const currentClientName = computed(() => {
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="sm:flex items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
||||
<div class="flex-1 flex items-center">
|
||||
<Field class="text-center pr-5">
|
||||
<FieldGroup>
|
||||
<FieldGroup class="flex-row items-end">
|
||||
<Field class="w-auto text-center">
|
||||
<FieldLabel for="color">Color</FieldLabel>
|
||||
<ProjectColorSelector v-model="project.color"></ProjectColorSelector>
|
||||
</Field>
|
||||
@@ -94,43 +95,30 @@ const currentClientName = computed(() => {
|
||||
autocomplete="projectName"
|
||||
@keydown.enter="submit()" />
|
||||
</Field>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="client">Client</FieldLabel>
|
||||
<FieldLabel for="client" :icon="UserCircleIcon">Client</FieldLabel>
|
||||
<ClientDropdown
|
||||
v-model="project.client_id"
|
||||
:create-client="createClient"
|
||||
:clients="activeClients">
|
||||
<template #trigger>
|
||||
<Badge
|
||||
tag="button"
|
||||
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>
|
||||
<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="currency"></ProjectEditBillableSection>
|
||||
</div>
|
||||
<div>
|
||||
<EstimatedTimeSection
|
||||
v-if="enableEstimatedTime"
|
||||
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="currency"></ProjectEditBillableSection>
|
||||
<EstimatedTimeSection
|
||||
v-if="enableEstimatedTime"
|
||||
v-model="project.estimated_time"
|
||||
@submit="submit()"></EstimatedTimeSection>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>
|
||||
|
||||
@@ -1,82 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { Field, FieldLabel } from '../field';
|
||||
import { Field, FieldDescription, FieldLabel } from '../field';
|
||||
import BillableRateInput from '@/packages/ui/src/Input/BillableRateInput.vue';
|
||||
import ProjectBillableSelect from '@/packages/ui/src/Project/ProjectBillableSelect.vue';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/Components/ui/select';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/packages/ui/src/tooltip';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import type { BillableKey } from '@/types/projects';
|
||||
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
|
||||
defineProps<{
|
||||
currency: string;
|
||||
}>();
|
||||
|
||||
const billableRateSelect = ref<BillableKey>('non-billable');
|
||||
const { organization } = useOrganizationQuery(getCurrentOrganizationId()!);
|
||||
|
||||
type RateType = 'default-rate' | 'custom-rate';
|
||||
|
||||
const billableDefault = ref<'billable' | 'non-billable'>('non-billable');
|
||||
const rateType = ref<RateType>('default-rate');
|
||||
|
||||
const billableRate = defineModel<number | null>('billableRate');
|
||||
const isBillable = defineModel<boolean>('isBillable');
|
||||
|
||||
onMounted(() => {
|
||||
if (isBillable.value === true) {
|
||||
if (billableRate.value) {
|
||||
billableRateSelect.value = 'custom-rate';
|
||||
} else {
|
||||
billableRateSelect.value = 'default-rate';
|
||||
}
|
||||
billableDefault.value = 'billable';
|
||||
rateType.value = billableRate.value ? 'custom-rate' : 'default-rate';
|
||||
}
|
||||
});
|
||||
|
||||
watch(billableRateSelect, () => {
|
||||
if (billableRateSelect.value === 'non-billable') {
|
||||
watch(billableDefault, () => {
|
||||
if (billableDefault.value === 'non-billable') {
|
||||
isBillable.value = false;
|
||||
billableRate.value = null;
|
||||
} else if (billableRateSelect.value === 'default-rate') {
|
||||
isBillable.value = true;
|
||||
billableRate.value = null;
|
||||
} else {
|
||||
isBillable.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
billableRateSelect.value = 'non-billable';
|
||||
|
||||
const billableOptionInfoTexts: { [key in BillableKey]: string } = {
|
||||
'non-billable': 'New time entries for this project will not be marked billable by default.',
|
||||
'default-rate':
|
||||
'New time entries for this project will be billable at the default rate by default.',
|
||||
'custom-rate':
|
||||
'New time entries for this project will be billable at a custom rate by default.',
|
||||
};
|
||||
|
||||
const billableOptionInfoText = computed(() => {
|
||||
return billableOptionInfoTexts[billableRateSelect.value];
|
||||
watch(rateType, () => {
|
||||
if (rateType.value === 'default-rate') {
|
||||
billableRate.value = null;
|
||||
} else if (rateType.value === 'custom-rate') {
|
||||
billableDefault.value = 'billable';
|
||||
isBillable.value = true;
|
||||
if (!billableRate.value) {
|
||||
billableRate.value = organization.value?.billable_rate ?? null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const displayedRate = computed({
|
||||
get() {
|
||||
if (rateType.value === 'default-rate') {
|
||||
return organization.value?.billable_rate ?? null;
|
||||
}
|
||||
return billableRate.value;
|
||||
},
|
||||
set(value: number | null) {
|
||||
if (rateType.value === 'custom-rate') {
|
||||
billableRate.value = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const billableDescription = computed(() => {
|
||||
if (billableDefault.value === 'non-billable') {
|
||||
return 'New time entries for this project will not be marked billable by default.';
|
||||
}
|
||||
return 'New time entries for this project will be marked billable by default.';
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sm:flex items-center space-y-2 sm:space-y-0 sm:space-x-4 pt-6">
|
||||
<Field>
|
||||
<div class="flex items-center space-x-1">
|
||||
<BillableIcon class="text-text-quaternary h-4 ml-1 mr-0.5"></BillableIcon>
|
||||
<FieldLabel for="billable">Billable Default</FieldLabel>
|
||||
</div>
|
||||
<ProjectBillableSelect v-model="billableRateSelect"></ProjectBillableSelect>
|
||||
</Field>
|
||||
<Field v-if="billableRateSelect === 'custom-rate'">
|
||||
<FieldLabel for="billableRate">Billable Rate</FieldLabel>
|
||||
<Field>
|
||||
<FieldLabel for="billable" :icon="BillableIcon">Billable Default</FieldLabel>
|
||||
<Select v-model="billableDefault">
|
||||
<SelectTrigger id="billable">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="non-billable">Non-billable</SelectItem>
|
||||
<SelectItem value="billable">Billable</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldDescription>{{ billableDescription }}</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel :icon="BillableIcon" for="billableRateType">Billable Rate</FieldLabel>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<Select v-model="rateType">
|
||||
<SelectTrigger id="billableRateType">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default-rate">Default Rate</SelectItem>
|
||||
<SelectItem value="custom-rate">Custom Rate</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TooltipProvider v-if="rateType === 'default-rate'">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<div>
|
||||
<BillableRateInput
|
||||
v-model="displayedRate"
|
||||
:currency="currency"
|
||||
disabled
|
||||
name="billableRate" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent> Uses the default rate of the organization </TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<BillableRateInput
|
||||
v-model="billableRate"
|
||||
v-else
|
||||
v-model="displayedRate"
|
||||
:currency="currency"
|
||||
name="billableRate"
|
||||
@keydown.enter="emit('submit')" />
|
||||
</Field>
|
||||
</div>
|
||||
<div class="flex items-center text-text-secondary text-xs pt-2 pl-1">
|
||||
<span>
|
||||
<span class="font-semibold"> Info: </span>
|
||||
{{ billableOptionInfoText }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user