Files
solidtime/resources/js/packages/ui/src/Project/ProjectEditBillableSection.vue
2026-10-13 14:59:12 +02:00

143 lines
4.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { Field, FieldDescription, FieldLabel } from '../field';
import BillableRateInput from '@/packages/ui/src/Input/BillableRateInput.vue';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '..';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/packages/ui/src/tooltip';
import { computed, onMounted, ref, watch } from 'vue';
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
import { useBillableRatesLock } from '../utils/useBillableRatesLock';
const props = defineProps<{
currency: string;
organizationBillableRate: number | null;
}>();
const { locked: billableRatesLocked } = useBillableRatesLock();
type RateType = 'default-rate' | 'custom-rate';
const billableDefault = ref<'billable' | 'non-billable'>('non-billable');
const rateType = ref<RateType>('default-rate');
const originallyBillable = ref(false);
const billableFlagLocked = computed(() => billableRatesLocked.value && !originallyBillable.value);
const billableRate = defineModel<number | null>('billableRate');
const isBillable = defineModel<boolean>('isBillable');
onMounted(() => {
originallyBillable.value = isBillable.value === true;
if (isBillable.value === true) {
billableDefault.value = 'billable';
rateType.value = billableRate.value ? 'custom-rate' : 'default-rate';
}
});
watch(billableDefault, () => {
if (billableDefault.value === 'non-billable') {
isBillable.value = false;
} else {
isBillable.value = true;
}
});
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 = props.organizationBillableRate ?? null;
}
}
});
const displayedRate = computed({
get() {
if (rateType.value === 'default-rate') {
return props.organizationBillableRate ?? null;
}
return billableRate.value;
},
set(value: number | null) {
if (rateType.value === 'custom-rate') {
billableRate.value = value;
}
},
});
const billableDescription = computed(() => {
if (billableFlagLocked.value) {
return 'Your organization’s plan does not allow enabling billable projects.';
}
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>
<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" :disabled="billableFlagLocked">Billable</SelectItem>
</SelectContent>
</Select>
<FieldDescription>{{ billableDescription }}</FieldDescription>
</Field>
<Field>
<FieldLabel :icon="BillableIcon" for="billableRateType">Billable Rate</FieldLabel>
<BillableRateInput
v-if="billableRatesLocked"
v-model="displayedRate"
:currency="currency"
name="billableRate" />
<div v-else 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-else
v-model="displayedRate"
:currency="currency"
name="billableRate"
@keydown.enter="emit('submit')" />
</div>
</Field>
</template>
<style scoped></style>