change billable rate input to use shadcn component

This commit is contained in:
Gregor Vostrak
2025-05-13 18:51:36 +02:00
parent d49082d7f3
commit 098ead8da6
5 changed files with 29 additions and 80 deletions

View File

@@ -154,7 +154,6 @@ const roleDescription = computed(() => {
class="flex-1">
<InputLabel
for="memberBillableRate"
class="mb-2"
value="Billable Rate" />
<BillableRateInput
v-model="

View File

@@ -48,7 +48,7 @@ const project = ref<CreateProjectBody>({
async function submit() {
if (props.originalProject.billable_rate !== project.value.billable_rate) {
//
// make sure that the alert modal is not immediately submitted when user presses enter
setTimeout(() => {
showBillableRateModal.value = true;
}, 0);
@@ -133,7 +133,7 @@ async function submitBillableRate() {
</ClientDropdown>
</div>
</div>
<div class="lg:grid grid-cols-2 gap-12">
<div>
<div>
<ProjectEditBillableSection
v-model:is-billable="project.is_billable"

View File

@@ -1,9 +1,13 @@
<script setup lang="ts">
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
import { formatCents } from '@/packages/ui/src/utils/money';
import { ref, watch, inject, type ComputedRef } from 'vue';
import { ref } from 'vue';
import { useFocus } from '@vueuse/core';
import type { Organization } from '@/packages/api/src';
import {
NumberField,
NumberFieldContent,
NumberFieldDecrement,
NumberFieldIncrement,
NumberFieldInput,
} from '@/Components/ui/number-field';
const props = defineProps<{
name: string;
@@ -11,8 +15,6 @@ const props = defineProps<{
currency: string;
}>();
const organization = inject<ComputedRef<Organization>>('organization');
const model = defineModel<number | null>({
default: null,
});
@@ -20,82 +22,32 @@ const model = defineModel<number | null>({
const billableRateInput = ref<HTMLInputElement | null>(null);
useFocus(billableRateInput, { initialValue: props.focus });
function cleanUpDecimalValue(value: string) {
value = value.replace(/,/g, '');
value = value.replace(props.currency, '');
return value.replace(/\./g, '');
}
function updateRate(value: string) {
value = value.trim();
if (value.includes(',')) {
const parts = value.split(',');
const lastPart = (parts[parts.length - 1] = parts[parts.length - 1]);
if (lastPart.length === 2) {
// we detected a decimal number with 2 digits after the comma
value = cleanUpDecimalValue(value);
model.value = parseInt(value);
}
} else if (value.includes('.')) {
const parts = value.split('.');
const lastPart = (parts[parts.length - 1] = parts[parts.length - 1]);
if (lastPart.length === 2) {
value = cleanUpDecimalValue(value);
model.value = parseInt(value);
}
} else if (value === '') {
model.value = 0;
} else {
// if it doesn't contain a comma or a dot, it's probably a whole number so let's convert it to cents
const parsedValue = parseInt(cleanUpDecimalValue(value)) * 100;
if (parsedValue) {
model.value = parsedValue;
} else {
model.value = 0;
}
}
inputValue.value = formatValue(model.value);
}
function formatValue(modelValue: number | null) {
const formattedValue = formatCents(
modelValue ?? 0,
props.currency,
organization?.value?.currency_format,
organization?.value?.currency_symbol,
organization?.value?.number_format
);
return formattedValue
?.replace(organization?.value?.currency_symbol ?? '', '')
.trim();
return modelValue ? modelValue / 100 : 0;
}
watch(model, (newValue) => {
inputValue.value = formatValue(newValue);
});
const inputValue = ref(formatValue(model.value));
</script>
<template>
<div class="relative">
<TextInput
<NumberField
:id="name"
ref="billableRateInput"
v-model="inputValue"
type="text"
:name="name"
placeholder="Billable Rate"
:model-value="formatValue(model)"
:step-snapping="false"
class="block w-full"
autocomplete="teamMemberRate"
@blur="updateRate($event.target.value)"
@keydown.enter="updateRate($event.target.value)" />
<div
class="absolute top-0 right-0 h-full flex items-center px-4 font-medium pointer-events-none">
<span>
{{ currency }}
</span>
</div>
:format-options="{
style: 'currency',
currency: currency,
currencyDisplay: 'code',
currencySign: 'accounting',
}"
@update:model-value="(value) => model = value * 100">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</div>
</template>

View File

@@ -128,7 +128,7 @@ const currentClientName = computed(() => {
</ClientDropdown>
</div>
</div>
<div class="lg:grid grid-cols-2 gap-12">
<div>
<div>
<ProjectEditBillableSection
v-model:is-billable="project.is_billable"

View File

@@ -66,9 +66,7 @@ const emit = defineEmits(['submit']);
v-model="billableRateSelect"
class="mt-2"></ProjectBillableSelect>
</div>
<div
v-if="billableRateSelect === 'custom-rate'"
class="sm:max-w-[120px]">
<div v-if="billableRateSelect === 'custom-rate'">
<InputLabel for="billableRate" value="Billable Rate" class="mb-2" />
<BillableRateInput
v-model="billableRate"