mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 12:42:15 +01:00
change billable rate input to use shadcn component
This commit is contained in:
@@ -154,7 +154,6 @@ const roleDescription = computed(() => {
|
|||||||
class="flex-1">
|
class="flex-1">
|
||||||
<InputLabel
|
<InputLabel
|
||||||
for="memberBillableRate"
|
for="memberBillableRate"
|
||||||
class="mb-2"
|
|
||||||
value="Billable Rate" />
|
value="Billable Rate" />
|
||||||
<BillableRateInput
|
<BillableRateInput
|
||||||
v-model="
|
v-model="
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const project = ref<CreateProjectBody>({
|
|||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (props.originalProject.billable_rate !== project.value.billable_rate) {
|
if (props.originalProject.billable_rate !== project.value.billable_rate) {
|
||||||
//
|
// make sure that the alert modal is not immediately submitted when user presses enter
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showBillableRateModal.value = true;
|
showBillableRateModal.value = true;
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -133,7 +133,7 @@ async function submitBillableRate() {
|
|||||||
</ClientDropdown>
|
</ClientDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:grid grid-cols-2 gap-12">
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ProjectEditBillableSection
|
<ProjectEditBillableSection
|
||||||
v-model:is-billable="project.is_billable"
|
v-model:is-billable="project.is_billable"
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
|
import { ref } from 'vue';
|
||||||
import { formatCents } from '@/packages/ui/src/utils/money';
|
|
||||||
import { ref, watch, inject, type ComputedRef } from 'vue';
|
|
||||||
import { useFocus } from '@vueuse/core';
|
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<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,8 +15,6 @@ const props = defineProps<{
|
|||||||
currency: string;
|
currency: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const organization = inject<ComputedRef<Organization>>('organization');
|
|
||||||
|
|
||||||
const model = defineModel<number | null>({
|
const model = defineModel<number | null>({
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
@@ -20,82 +22,32 @@ const model = defineModel<number | null>({
|
|||||||
const billableRateInput = ref<HTMLInputElement | null>(null);
|
const billableRateInput = ref<HTMLInputElement | null>(null);
|
||||||
useFocus(billableRateInput, { initialValue: props.focus });
|
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) {
|
function formatValue(modelValue: number | null) {
|
||||||
const formattedValue = formatCents(
|
return modelValue ? modelValue / 100 : 0;
|
||||||
modelValue ?? 0,
|
|
||||||
props.currency,
|
|
||||||
organization?.value?.currency_format,
|
|
||||||
organization?.value?.currency_symbol,
|
|
||||||
organization?.value?.number_format
|
|
||||||
);
|
|
||||||
return formattedValue
|
|
||||||
?.replace(organization?.value?.currency_symbol ?? '', '')
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(model, (newValue) => {
|
|
||||||
inputValue.value = formatValue(newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputValue = ref(formatValue(model.value));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<TextInput
|
<NumberField
|
||||||
:id="name"
|
:id="name"
|
||||||
ref="billableRateInput"
|
ref="billableRateInput"
|
||||||
v-model="inputValue"
|
:model-value="formatValue(model)"
|
||||||
type="text"
|
:step-snapping="false"
|
||||||
:name="name"
|
|
||||||
placeholder="Billable Rate"
|
|
||||||
class="block w-full"
|
class="block w-full"
|
||||||
autocomplete="teamMemberRate"
|
:format-options="{
|
||||||
@blur="updateRate($event.target.value)"
|
style: 'currency',
|
||||||
@keydown.enter="updateRate($event.target.value)" />
|
currency: currency,
|
||||||
<div
|
currencyDisplay: 'code',
|
||||||
class="absolute top-0 right-0 h-full flex items-center px-4 font-medium pointer-events-none">
|
currencySign: 'accounting',
|
||||||
<span>
|
}"
|
||||||
{{ currency }}
|
@update:model-value="(value) => model = value * 100">
|
||||||
</span>
|
<NumberFieldContent>
|
||||||
</div>
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ const currentClientName = computed(() => {
|
|||||||
</ClientDropdown>
|
</ClientDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:grid grid-cols-2 gap-12">
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ProjectEditBillableSection
|
<ProjectEditBillableSection
|
||||||
v-model:is-billable="project.is_billable"
|
v-model:is-billable="project.is_billable"
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ const emit = defineEmits(['submit']);
|
|||||||
v-model="billableRateSelect"
|
v-model="billableRateSelect"
|
||||||
class="mt-2"></ProjectBillableSelect>
|
class="mt-2"></ProjectBillableSelect>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="billableRateSelect === 'custom-rate'">
|
||||||
v-if="billableRateSelect === 'custom-rate'"
|
|
||||||
class="sm:max-w-[120px]">
|
|
||||||
<InputLabel for="billableRate" value="Billable Rate" class="mb-2" />
|
<InputLabel for="billableRate" value="Billable Rate" class="mb-2" />
|
||||||
<BillableRateInput
|
<BillableRateInput
|
||||||
v-model="billableRate"
|
v-model="billableRate"
|
||||||
|
|||||||
Reference in New Issue
Block a user