mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
move time entry mass updates to ui package and remove its dependencies
This commit is contained in:
@@ -39,6 +39,7 @@ const { clients } = storeToRefs(useClientsStore());
|
||||
const gridTemplate = computed(() => {
|
||||
return `grid-template-columns: minmax(300px, 1fr) minmax(150px, auto) minmax(140px, auto) minmax(130px, auto) ${props.showBillableRate ? 'minmax(130px, auto)' : ''} minmax(120px, auto) 80px;`;
|
||||
});
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -47,6 +48,7 @@ const gridTemplate = computed(() => {
|
||||
:createClient
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:clients="clients"
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction"
|
||||
v-model:show="showCreateProjectModal"></ProjectCreateModal>
|
||||
<div class="flow-root max-w-[100vw] overflow-x-auto">
|
||||
<div class="inline-block min-w-full align-middle">
|
||||
|
||||
@@ -39,6 +39,10 @@ const { createTimeEntry } = useTimeEntriesStore();
|
||||
const show = defineModel('show', { default: false });
|
||||
const saving = ref(false);
|
||||
|
||||
defineProps<{
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
async function createProject(
|
||||
project: CreateProjectBody
|
||||
): Promise<Project | undefined> {
|
||||
@@ -135,6 +139,7 @@ async function createTag(tag: string) {
|
||||
size="xlarge"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:enableEstimatedTime="enableEstimatedTime"
|
||||
v-model:project="timeEntry.project_id"
|
||||
v-model:task="
|
||||
timeEntry.task_id
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
import TimeTrackerRunningInDifferentOrganizationOverlay from '@/packages/ui/src/TimeTracker/TimeTrackerRunningInDifferentOrganizationOverlay.vue';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
|
||||
const page = usePage<{
|
||||
auth: {
|
||||
@@ -111,6 +112,7 @@ const { tags } = storeToRefs(useTagsStore());
|
||||
|
||||
<TimeTrackerControls
|
||||
:createProject
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
:createClient
|
||||
:clients
|
||||
:tags
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import { getCurrentRole } from '@/utils/useUser';
|
||||
import { useOrganizationStore } from '@/utils/useOrganization';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
|
||||
onMounted(() => {
|
||||
useProjectsStore().fetchProjects();
|
||||
@@ -94,6 +95,7 @@ const showBillableRate = computed(() => {
|
||||
</SecondaryButton>
|
||||
<ProjectCreateModal
|
||||
:createProject
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction"
|
||||
:createClient
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:clients="clients"
|
||||
|
||||
@@ -64,7 +64,8 @@ import {
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
|
||||
import TimeEntryMassActionRow from '@/Components/Common/TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import TimeEntryMassActionRow from '@/packages/ui/src/TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
|
||||
const startDate = useSessionStorage<string>(
|
||||
'reporting-start-date',
|
||||
@@ -118,7 +119,8 @@ function getFilterAttributes() {
|
||||
const currentTimeEntryStore = useCurrentTimeEntryStore();
|
||||
const { currentTimeEntry } = storeToRefs(currentTimeEntryStore);
|
||||
const { setActiveState, startLiveTimer } = currentTimeEntryStore;
|
||||
const { createTimeEntry, updateTimeEntry } = useTimeEntriesStore();
|
||||
const { createTimeEntry, updateTimeEntry, updateTimeEntries } =
|
||||
useTimeEntriesStore();
|
||||
|
||||
const { tags } = storeToRefs(useTagsStore());
|
||||
|
||||
@@ -338,13 +340,27 @@ async function clearSelectionAndState() {
|
||||
</div>
|
||||
<TimeEntryMassActionRow
|
||||
:selected-time-entries="selectedTimeEntries"
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
@submit="clearSelectionAndState"
|
||||
:delete-selected="deleteSelected"
|
||||
@select-all="selectedTimeEntries = [...timeEntries]"
|
||||
@unselect-all="selectedTimeEntries = []"
|
||||
:all-selected="
|
||||
selectedTimeEntries.length === timeEntries.length
|
||||
"></TimeEntryMassActionRow>
|
||||
:all-selected="selectedTimeEntries.length === timeEntries.length"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:tags="tags"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:clients="clients"
|
||||
:update-time-entries="
|
||||
(args) =>
|
||||
updateTimeEntries(
|
||||
selectedTimeEntries.map((timeEntry) => timeEntry.id),
|
||||
args
|
||||
)
|
||||
"
|
||||
:create-project="createProject"
|
||||
:create-client="createClient"
|
||||
:createTag="createTag"></TimeEntryMassActionRow>
|
||||
<div class="w-full relative">
|
||||
<div v-for="entry in timeEntries" :key="entry.id">
|
||||
<TimeEntryRow
|
||||
@@ -357,6 +373,7 @@ async function clearSelectionAndState() {
|
||||
"
|
||||
:createClient
|
||||
:createProject
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:tags="tags"
|
||||
|
||||
@@ -26,14 +26,19 @@ import { useTagsStore } from '@/utils/useTags';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import TimeEntryCreateModal from '@/Components/Common/TimeEntry/TimeEntryCreateModal.vue';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import TimeEntryMassActionRow from '@/Components/Common/TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import TimeEntryMassActionRow from '@/packages/ui/src/TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import type { UpdateMultipleTimeEntriesChangeset } from '@/packages/api/src';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
|
||||
const timeEntriesStore = useTimeEntriesStore();
|
||||
const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore);
|
||||
const { updateTimeEntry, fetchTimeEntries, createTimeEntry } =
|
||||
useTimeEntriesStore();
|
||||
|
||||
async function updateTimeEntries(ids: string[], changes: Partial<TimeEntry>) {
|
||||
async function updateTimeEntries(
|
||||
ids: string[],
|
||||
changes: UpdateMultipleTimeEntriesChangeset
|
||||
) {
|
||||
await useTimeEntriesStore().updateTimeEntries(ids, changes);
|
||||
fetchTimeEntries();
|
||||
}
|
||||
@@ -114,6 +119,7 @@ function deleteSelected() {
|
||||
|
||||
<template>
|
||||
<TimeEntryCreateModal
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
v-model:show="showManualTimeEntryModal"></TimeEntryCreateModal>
|
||||
<AppLayout title="Dashboard" data-testid="time_view">
|
||||
<MainContainer
|
||||
@@ -135,14 +141,31 @@ function deleteSelected() {
|
||||
</MainContainer>
|
||||
<TimeEntryMassActionRow
|
||||
:selected-time-entries="selectedTimeEntries"
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
@submit="clearSelectionAndState"
|
||||
:all-selected="selectedTimeEntries.length === timeEntries.length"
|
||||
@select-all="selectedTimeEntries = [...timeEntries]"
|
||||
@unselect-all="selectedTimeEntries = []"
|
||||
:delete-selected="deleteSelected"></TimeEntryMassActionRow>
|
||||
:delete-selected="deleteSelected"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:tags="tags"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:clients="clients"
|
||||
:update-time-entries="
|
||||
(args) =>
|
||||
updateTimeEntries(
|
||||
selectedTimeEntries.map((timeEntry) => timeEntry.id),
|
||||
args
|
||||
)
|
||||
"
|
||||
:create-project="createProject"
|
||||
:create-client="createClient"
|
||||
:createTag="createTag"></TimeEntryMassActionRow>
|
||||
<TimeEntryGroupedTable
|
||||
v-model:selected="selectedTimeEntries"
|
||||
:createProject
|
||||
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
|
||||
:clients
|
||||
:createClient
|
||||
:updateTimeEntry
|
||||
|
||||
@@ -19,7 +19,6 @@ import EstimatedTimeSection from '@/packages/ui/src/EstimatedTimeSection.vue';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import ProjectEditBillableSection from '@/packages/ui/src/Project/ProjectEditBillableSection.vue';
|
||||
import type { Client } from '@/packages/api/src';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
const saving = ref(false);
|
||||
@@ -29,6 +28,7 @@ const props = defineProps<{
|
||||
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const activeClients = computed(() => {
|
||||
@@ -138,7 +138,7 @@ const currentClientName = computed(() => {
|
||||
</div>
|
||||
<div>
|
||||
<EstimatedTimeSection
|
||||
v-if="isAllowedToPerformPremiumAction()"
|
||||
v-if="enableEstimatedTime"
|
||||
@submit="submit()"
|
||||
v-model="project.estimated_time"></EstimatedTimeSection>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,7 @@ const props = defineProps<{
|
||||
deleteTimeEntries: (timeEntries: TimeEntry[]) => void;
|
||||
currency: string;
|
||||
selectedTimeEntries: TimeEntry[];
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
selected: [TimeEntry[]];
|
||||
@@ -125,6 +126,7 @@ function onSelectChange(event: Event) {
|
||||
:showBadgeBorder="false"
|
||||
@changed="updateProjectAndTask"
|
||||
:project="timeEntry.project_id"
|
||||
:enableEstimatedTime
|
||||
:currency="currency"
|
||||
:task="
|
||||
timeEntry.task_id
|
||||
@@ -174,6 +176,7 @@ function onSelectChange(event: Event) {
|
||||
class="w-full border-t border-default-background-separator bg-black/15">
|
||||
<TimeEntryRow
|
||||
:projects="projects"
|
||||
:enableEstimatedTime
|
||||
:tasks="tasks"
|
||||
:selected="
|
||||
!!selectedTimeEntries.find(
|
||||
|
||||
@@ -37,6 +37,7 @@ const props = defineProps<{
|
||||
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const groupedTimeEntries = computed(() => {
|
||||
@@ -160,6 +161,7 @@ function unselectAllTimeEntries(value: TimeEntriesGroupedByType[]) {
|
||||
<template v-for="entry in value" :key="entry.id">
|
||||
<TimeEntryAggregateRow
|
||||
:createProject
|
||||
:enableEstimatedTime
|
||||
:selected-time-entries="selectedTimeEntries"
|
||||
@selected="
|
||||
(timeEntries) => {
|
||||
@@ -194,6 +196,7 @@ function unselectAllTimeEntries(value: TimeEntriesGroupedByType[]) {
|
||||
:time-entry="entry"></TimeEntryAggregateRow>
|
||||
<TimeEntryRow
|
||||
:createClient
|
||||
:enableEstimatedTime
|
||||
:createProject
|
||||
:projects="projects"
|
||||
:selected="
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import MainContainer from '@/packages/ui/src/MainContainer.vue';
|
||||
import { PencilSquareIcon, TrashIcon } from '@heroicons/vue/20/solid';
|
||||
import TimeEntryMassUpdateModal from '@/Components/Common/TimeEntry/TimeEntryMassUpdateModal.vue';
|
||||
import type { TimeEntry } from '@/packages/api/src';
|
||||
import TimeEntryMassUpdateModal from '@/packages/ui/src/TimeEntry/TimeEntryMassUpdateModal.vue';
|
||||
import type {
|
||||
Client,
|
||||
CreateClientBody,
|
||||
CreateProjectBody,
|
||||
Project,
|
||||
Tag,
|
||||
Task,
|
||||
TimeEntry,
|
||||
UpdateMultipleTimeEntriesChangeset,
|
||||
} from '@/packages/api/src';
|
||||
import { ref } from 'vue';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Checkbox, InputLabel } from '@/packages/ui/src';
|
||||
@@ -12,6 +21,18 @@ const props = defineProps<{
|
||||
deleteSelected: () => void;
|
||||
class?: string;
|
||||
allSelected: boolean;
|
||||
projects: Project[];
|
||||
tasks: Task[];
|
||||
tags: Tag[];
|
||||
clients: Client[];
|
||||
createTag: (name: string) => Promise<Tag | undefined>;
|
||||
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
updateTimeEntries: (
|
||||
changeset: UpdateMultipleTimeEntriesChangeset
|
||||
) => Promise<void>;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -25,6 +46,16 @@ const showMassUpdateModal = ref(false);
|
||||
|
||||
<template>
|
||||
<TimeEntryMassUpdateModal
|
||||
:projects
|
||||
:tasks
|
||||
:tags
|
||||
:clients
|
||||
:createTag
|
||||
:createProject
|
||||
:createClient
|
||||
:updateTimeEntries
|
||||
:enableEstimatedTime
|
||||
:currency
|
||||
:time-entries="selectedTimeEntries"
|
||||
@submit="emit('submit')"
|
||||
v-model:show="showMassUpdateModal"></TimeEntryMassUpdateModal>
|
||||
@@ -1,66 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
|
||||
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
|
||||
import TextInput from '../Input/TextInput.vue';
|
||||
import SecondaryButton from '../Buttons/SecondaryButton.vue';
|
||||
import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import PrimaryButton from '../Buttons/PrimaryButton.vue';
|
||||
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTasksStore } from '@/utils/useTasks';
|
||||
import { useProjectsStore } from '@/utils/useProjects';
|
||||
import { useTagsStore } from '@/utils/useTags';
|
||||
import InputLabel from '../Input/InputLabel.vue';
|
||||
import {
|
||||
type CreateClientBody,
|
||||
type CreateProjectBody,
|
||||
type Project,
|
||||
type Client,
|
||||
api,
|
||||
type TimeEntry,
|
||||
type UpdateMultipleTimeEntriesChangeset,
|
||||
} from '@/packages/api/src';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import { Badge, Checkbox } from '@/packages/ui/src';
|
||||
import SelectDropdown from '../../../packages/ui/src/Input/SelectDropdown.vue';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
import SelectDropdown from '../Input/SelectDropdown.vue';
|
||||
import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue';
|
||||
const projectStore = useProjectsStore();
|
||||
const { projects } = storeToRefs(projectStore);
|
||||
const taskStore = useTasksStore();
|
||||
const { tasks } = storeToRefs(taskStore);
|
||||
const clientStore = useClientsStore();
|
||||
const { clients } = storeToRefs(clientStore);
|
||||
import type { Tag, Task } from '@/packages/api/src';
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
const saving = ref(false);
|
||||
|
||||
async function createProject(
|
||||
project: CreateProjectBody
|
||||
): Promise<Project | undefined> {
|
||||
return await useProjectsStore().createProject(project);
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
timeEntries: TimeEntry[];
|
||||
projects: Project[];
|
||||
tasks: Task[];
|
||||
clients: Client[];
|
||||
tags: Tag[];
|
||||
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
createTag: (name: string) => Promise<Tag | undefined>;
|
||||
updateTimeEntries: (
|
||||
changeset: UpdateMultipleTimeEntriesChangeset
|
||||
) => Promise<void>;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
async function createClient(
|
||||
body: CreateClientBody
|
||||
): Promise<Client | undefined> {
|
||||
return await useClientsStore().createClient(body);
|
||||
}
|
||||
|
||||
const descriptionInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
|
||||
watch(show, (value) => {
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
@@ -75,11 +58,6 @@ const projectId = ref<string | null>(null);
|
||||
const billable = ref<boolean | undefined>(undefined);
|
||||
const selectedTags = ref<string[]>([]);
|
||||
|
||||
const { tags } = storeToRefs(useTagsStore());
|
||||
async function createTag(tag: string) {
|
||||
return await useTagsStore().createTag(tag);
|
||||
}
|
||||
|
||||
const timeEntryBillable = computed({
|
||||
get: () => {
|
||||
if (billable.value === undefined) {
|
||||
@@ -99,71 +77,48 @@ const timeEntryBillable = computed({
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
saving.value = true;
|
||||
if (organizationId) {
|
||||
const timeEntryUpdatesBody = {} as UpdateMultipleTimeEntriesChangeset;
|
||||
if (description.value && description.value !== '') {
|
||||
timeEntryUpdatesBody.description = description.value;
|
||||
const timeEntryUpdatesBody = {} as UpdateMultipleTimeEntriesChangeset;
|
||||
if (description.value && description.value !== '') {
|
||||
timeEntryUpdatesBody.description = description.value;
|
||||
}
|
||||
if (projectId.value !== null) {
|
||||
if (projectId.value === '') {
|
||||
// "No Project" is selected
|
||||
timeEntryUpdatesBody.project_id = null;
|
||||
} else {
|
||||
timeEntryUpdatesBody.project_id = projectId.value;
|
||||
}
|
||||
if (projectId.value !== null) {
|
||||
if (projectId.value === '') {
|
||||
// "No Project" is selected
|
||||
timeEntryUpdatesBody.project_id = null;
|
||||
} else {
|
||||
timeEntryUpdatesBody.project_id = projectId.value;
|
||||
}
|
||||
timeEntryUpdatesBody.task_id = null;
|
||||
if (taskId.value !== undefined) {
|
||||
timeEntryUpdatesBody.task_id = taskId.value;
|
||||
}
|
||||
timeEntryUpdatesBody.task_id = null;
|
||||
if (taskId.value !== undefined) {
|
||||
timeEntryUpdatesBody.task_id = taskId.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (billable.value !== undefined) {
|
||||
timeEntryUpdatesBody.billable = billable.value;
|
||||
}
|
||||
if (selectedTags.value.length > 0) {
|
||||
timeEntryUpdatesBody.tags = selectedTags.value;
|
||||
}
|
||||
if (removeAllTags.value) {
|
||||
timeEntryUpdatesBody.tags = [];
|
||||
}
|
||||
if (billable.value !== undefined) {
|
||||
timeEntryUpdatesBody.billable = billable.value;
|
||||
}
|
||||
if (selectedTags.value.length > 0) {
|
||||
timeEntryUpdatesBody.tags = selectedTags.value;
|
||||
}
|
||||
if (removeAllTags.value) {
|
||||
timeEntryUpdatesBody.tags = [];
|
||||
}
|
||||
|
||||
try {
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateMultipleTimeEntries(
|
||||
{
|
||||
ids: props.timeEntries.map(
|
||||
(timeEntry) => timeEntry.id
|
||||
),
|
||||
changes: {
|
||||
...timeEntryUpdatesBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}
|
||||
),
|
||||
'Time entries updated',
|
||||
'Failed to update time entries',
|
||||
() => {
|
||||
show.value = false;
|
||||
emit('submit');
|
||||
description.value = '';
|
||||
projectId.value = null;
|
||||
taskId.value = undefined;
|
||||
selectedTags.value = [];
|
||||
billable.value = undefined;
|
||||
saving.value = false;
|
||||
removeAllTags.value = false;
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
saving.value = false;
|
||||
}
|
||||
try {
|
||||
await props.updateTimeEntries({ ...timeEntryUpdatesBody });
|
||||
|
||||
show.value = false;
|
||||
emit('submit');
|
||||
description.value = '';
|
||||
projectId.value = null;
|
||||
taskId.value = undefined;
|
||||
selectedTags.value = [];
|
||||
billable.value = undefined;
|
||||
saving.value = false;
|
||||
removeAllTags.value = false;
|
||||
} catch (e) {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
const removeAllTags = ref(false);
|
||||
@@ -200,11 +155,12 @@ watch(removeAllTags, () => {
|
||||
:clients
|
||||
:createProject
|
||||
:createClient
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
:currency="currency"
|
||||
class="mt-1"
|
||||
empty-placeholder="Select project..."
|
||||
allow-reset
|
||||
size="xlarge"
|
||||
:enableEstimatedTime
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
v-model:project="projectId"
|
||||
@@ -38,6 +38,7 @@ const props = defineProps<{
|
||||
showMember?: boolean;
|
||||
showDate?: boolean;
|
||||
selected?: boolean;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{ selected: []; unselected: [] }>();
|
||||
@@ -117,6 +118,7 @@ function onSelectChange(event: Event) {
|
||||
@changed="updateProjectAndTask"
|
||||
:project="timeEntry.project_id"
|
||||
:currency="currency"
|
||||
:enableEstimatedTime
|
||||
:task="
|
||||
timeEntry.task_id
|
||||
"></TimeTrackerProjectTaskDropdown>
|
||||
|
||||
@@ -33,6 +33,7 @@ const props = defineProps<{
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
isActive: boolean;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -92,7 +93,7 @@ function updateTimeEntryDescription() {
|
||||
class="flex items-center relative @container"
|
||||
data-testid="dashboard_timer">
|
||||
<div
|
||||
class="flex flex-col lg:flex-row w-full justify-between rounded-lg bg-card-background border-card-border border transition shadow-card">
|
||||
class="flex flex-col @2xl:flex-row w-full justify-between rounded-lg bg-card-background border-card-border border transition shadow-card">
|
||||
<div class="flex flex-1 items-center pr-6">
|
||||
<input
|
||||
placeholder="What are you working on?"
|
||||
@@ -101,12 +102,12 @@ function updateTimeEntryDescription() {
|
||||
v-model="tempDescription"
|
||||
@keydown.enter="startTimerIfNotActive"
|
||||
@blur="updateTimeEntryDescription"
|
||||
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator lg:px-4 text-base @4xl:text-lg text-white font-medium bg-transparent border-none placeholder-muted focus:ring-0 transition"
|
||||
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator @2xl:px-4 text-base @4xl:text-lg text-white font-medium bg-transparent border-none placeholder-muted focus:ring-0 transition"
|
||||
type="text" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between pl-2 shrink min-w-0">
|
||||
<div
|
||||
class="flex items-center w-[130px] sm:w-auto shrink min-w-0">
|
||||
class="flex items-center w-[130px] @2xl:w-auto shrink min-w-0">
|
||||
<TimeTrackerProjectTaskDropdown
|
||||
:createClient
|
||||
:clients
|
||||
@@ -115,12 +116,13 @@ function updateTimeEntryDescription() {
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
@changed="updateProject"
|
||||
:enableEstimatedTime="enableEstimatedTime"
|
||||
v-model:project="currentTimeEntry.project_id"
|
||||
v-model:task="
|
||||
currentTimeEntry.task_id
|
||||
"></TimeTrackerProjectTaskDropdown>
|
||||
</div>
|
||||
<div class="flex items-center lg:space-x-2 px-2 lg:px-4">
|
||||
<div class="flex items-center @2xl:space-x-2 px-2 @2xl:px-4">
|
||||
<TimeTrackerTagDropdown
|
||||
@changed="$emit('updateTimeEntry')"
|
||||
:createTag
|
||||
@@ -149,7 +151,7 @@ function updateTimeEntryDescription() {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pl-4 lg:pl-6 pr-3 absolute sm:relative top-[6px] sm:top-0 right-0">
|
||||
class="pl-4 @2xl:pl-6 pr-3 absolute sm:relative top-[6px] sm:top-0 right-0">
|
||||
<TimeTrackerStartStop
|
||||
:active="isActive"
|
||||
@changed="onToggleButtonPress"
|
||||
|
||||
@@ -63,6 +63,7 @@ const props = withDefaults(
|
||||
currency: string;
|
||||
emptyPlaceholder: string;
|
||||
allowReset: boolean;
|
||||
enableEstimatedTime: boolean;
|
||||
}>(),
|
||||
{
|
||||
showBadgeBorder: true,
|
||||
@@ -704,6 +705,7 @@ const showCreateProject = ref(false);
|
||||
</Dropdown>
|
||||
<ProjectCreateModal
|
||||
:createClient
|
||||
:enableEstimatedTime="enableEstimatedTime"
|
||||
:currency="currency"
|
||||
:clients="clients"
|
||||
:createProject
|
||||
|
||||
@@ -25,6 +25,7 @@ import SelectDropdown from './Input/SelectDropdown.vue';
|
||||
import Badge from './Badge.vue';
|
||||
import Checkbox from './Input/Checkbox.vue';
|
||||
import TimeEntryGroupedTable from './TimeEntry/TimeEntryGroupedTable.vue';
|
||||
import TimeEntryMassActionRow from './TimeEntry/TimeEntryMassActionRow.vue';
|
||||
|
||||
export {
|
||||
money,
|
||||
@@ -46,4 +47,5 @@ export {
|
||||
Badge,
|
||||
Checkbox,
|
||||
TimeEntryGroupedTable,
|
||||
TimeEntryMassActionRow,
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/packages/api/src';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
import type { UpdateMultipleTimeEntriesChangeset } from '@/packages/api/src';
|
||||
|
||||
export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
||||
const timeEntries = ref<TimeEntry[]>(reactive([]));
|
||||
@@ -83,7 +84,7 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
||||
|
||||
async function updateTimeEntries(
|
||||
ids: string[],
|
||||
changes: Partial<TimeEntry>
|
||||
changes: UpdateMultipleTimeEntriesChangeset
|
||||
) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
|
||||
Reference in New Issue
Block a user