mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 11:42:15 +01:00
add billable rate updates for time entries in the past to projects and project members, fixes ST-304
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||
import DialogModal from '@/Components/DialogModal.vue';
|
||||
import PrimaryButton from '@/Components/PrimaryButton.vue';
|
||||
import { formatCents } from '../../../utils/money';
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
const saving = defineModel('saving', { default: false });
|
||||
|
||||
defineProps<{
|
||||
newBillableRate?: number | null;
|
||||
memberName: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [billable_rate_update_time_entries: boolean];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogModal closeable :show="show" @close="show = false">
|
||||
<template #title>
|
||||
<div class="flex justify-center">
|
||||
<span> Update Project Member Billable Rate </span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1">
|
||||
<p class="py-1 text-center">
|
||||
The billable rate of {{ memberName }} will be updated to
|
||||
<strong>{{
|
||||
newBillableRate
|
||||
? formatCents(newBillableRate)
|
||||
: ' the default rate of the organization'
|
||||
}}</strong
|
||||
>.
|
||||
</p>
|
||||
<p class="py-1 text-center font-semibold max-w-md mx-auto">
|
||||
Do you want to update all existing time entries, where
|
||||
the project member billable rate applies as well?
|
||||
</p>
|
||||
|
||||
<div class="space-x-3 pt-5 pb-2 flex justify-center">
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': saving }"
|
||||
:disabled="saving"
|
||||
@click="emit('submit', true)">
|
||||
Yes, update existing time entries
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': saving }"
|
||||
:disabled="saving"
|
||||
@click="emit('submit', false)">
|
||||
No, only for new time entries
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
<p class="text-center pt-3 pb-1">
|
||||
Learn more about the
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://docs.solidtime.io/user-guide/billable-rates"
|
||||
class="text-blue-400 hover:text-blue-500 transition"
|
||||
>billable rate logic
|
||||
<ArrowTopRightOnSquareIcon
|
||||
class="w-4 -mt-0.5 inline-block"></ArrowTopRightOnSquareIcon
|
||||
></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -8,6 +8,8 @@ import { useFocus } from '@vueuse/core';
|
||||
import { useProjectMembersStore } from '@/utils/useProjectMembers';
|
||||
import BillableRateInput from '@/Components/Common/BillableRateInput.vue';
|
||||
import { UserIcon } from '@heroicons/vue/24/solid';
|
||||
import ProjectMemberBillableRateModal from '@/Components/Common/ProjectMember/ProjectMemberBillableRateModal.vue';
|
||||
import InputLabel from '@/Components/InputLabel.vue';
|
||||
const { updateProjectMember } = useProjectMembersStore();
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -21,8 +23,15 @@ const props = defineProps<{
|
||||
const projectMemberBody = ref<UpdateProjectMemberBody>({
|
||||
billable_rate: props.projectMember.billable_rate,
|
||||
});
|
||||
|
||||
const showBillableRateModal = ref(false);
|
||||
async function submit() {
|
||||
if (
|
||||
props.projectMember.billable_rate !==
|
||||
projectMemberBody.value.billable_rate
|
||||
) {
|
||||
showBillableRateModal.value = true;
|
||||
return;
|
||||
}
|
||||
await updateProjectMember(props.projectMember.id, projectMemberBody.value);
|
||||
show.value = false;
|
||||
projectMemberBody.value = {
|
||||
@@ -30,6 +39,14 @@ async function submit() {
|
||||
};
|
||||
}
|
||||
|
||||
async function submitBillableRate(billableRateUpdateTimeEntries: boolean) {
|
||||
projectMemberBody.value.billable_rate_update_time_entries =
|
||||
billableRateUpdateTimeEntries;
|
||||
await updateProjectMember(props.projectMember.id, projectMemberBody.value);
|
||||
show.value = false;
|
||||
showBillableRateModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => show.value,
|
||||
(value) => {
|
||||
@@ -55,6 +72,11 @@ useFocus(projectNameInput, { initialValue: true });
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<ProjectMemberBillableRateModal
|
||||
member-name="props.name"
|
||||
v-model:show="showBillableRateModal"
|
||||
@close="showBillableRateModal = false"
|
||||
@submit="submitBillableRate"></ProjectMemberBillableRateModal>
|
||||
<div class="grid grid-cols-3 items-center space-x-4">
|
||||
<div
|
||||
class="col-span-3 sm:col-span-2 space-x-2 flex items-center">
|
||||
@@ -62,6 +84,9 @@ useFocus(projectNameInput, { initialValue: true });
|
||||
<span>{{ props.name }}</span>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-1 flex-1">
|
||||
<InputLabel
|
||||
for="billable_rate"
|
||||
value="Billable Rate"></InputLabel>
|
||||
<BillableRateInput
|
||||
@keydown.enter="submit"
|
||||
name="billable_rate"
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ProjectMember } from '@/utils/api';
|
||||
import { useMembersStore } from '@/utils/useMembers';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
delete: [];
|
||||
edit: [];
|
||||
@@ -25,22 +26,24 @@ const currentMember = computed(() => {
|
||||
<template>
|
||||
<Dropdown>
|
||||
<template #trigger>
|
||||
<svg
|
||||
data-testid="project_actions"
|
||||
<button
|
||||
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity"
|
||||
:aria-label="
|
||||
'Actions for Project Member ' + currentMember?.name
|
||||
"
|
||||
class="h-10 w-10 p-2 rounded-full hover:bg-card-background opacity-20 group-hover:opacity-100 transition"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
|
||||
</svg>
|
||||
">
|
||||
<svg
|
||||
class="h-10 w-10 p-2 rounded-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<button
|
||||
|
||||
@@ -24,7 +24,7 @@ const createProjectMember = ref(false);
|
||||
<div class="flow-root">
|
||||
<div class="inline-block min-w-full align-middle">
|
||||
<div
|
||||
data-testid="project_table"
|
||||
data-testid="project_member_table"
|
||||
class="grid min-w-full"
|
||||
style="grid-template-columns: 1fr 150px 150px 80px">
|
||||
<ProjectMemberTableHeading></ProjectMemberTableHeading>
|
||||
|
||||
Reference in New Issue
Block a user