add mass update to detailed reporting page

This commit is contained in:
Gregor Vostrak
2024-10-02 21:35:38 +02:00
parent fcba96fbf6
commit a77b8a5ed2
15 changed files with 779 additions and 94 deletions

View File

@@ -28,6 +28,14 @@ export type CreateTimeEntryBody = ZodiosBodyByAlias<
'createTimeEntry'
>;
export type UpdateMultipleTimeEntriesBody = ZodiosBodyByAlias<
SolidTimeApi,
'updateMultipleTimeEntries'
>;
export type UpdateMultipleTimeEntriesChangeset =
UpdateMultipleTimeEntriesBody['changes'];
export type ProjectResponse = ZodiosResponseByAlias<
SolidTimeApi,
'getProjects'

View File

@@ -170,7 +170,6 @@ const TimeEntryResource = z
billable: z.boolean(),
})
.passthrough();
const TimeEntryCollection = z.array(TimeEntryResource);
const TimeEntryStoreRequest = z
.object({
member_id: z.string().uuid(),
@@ -270,7 +269,6 @@ export const schemas = {
TaskUpdateRequest,
start,
TimeEntryResource,
TimeEntryCollection,
TimeEntryStoreRequest,
TimeEntryUpdateMultipleRequest,
TimeEntryUpdateRequest,
@@ -2147,6 +2145,11 @@ Users with the permission &#x60;time-entries:view:own&#x60; can only use this en
type: 'Query',
schema: z.number().int().gte(1).lte(500).optional(),
},
{
name: 'offset',
type: 'Query',
schema: z.number().int().gte(0).optional(),
},
{
name: 'only_full_dates',
type: 'Query',
@@ -2183,7 +2186,12 @@ Users with the permission &#x60;time-entries:view:own&#x60; can only use this en
schema: z.string().optional(),
},
],
response: z.object({ data: TimeEntryCollection }).passthrough(),
response: z
.object({
data: z.array(TimeEntryResource),
meta: z.object({ total: z.number().int() }).passthrough(),
})
.passthrough(),
errors: [
{
status: 401,

View File

@@ -39,7 +39,7 @@ const borderClasses = computed(() => {
twMerge(
badgeClasses[size],
borderClasses,
'rounded inline-flex items-center font-semibold text-white',
'rounded inline-flex items-center font-semibold text-white outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
props.class
)
">

View File

@@ -94,7 +94,7 @@ function setLastYear() {
@submit="emit('submit')">
<template #trigger>
<button
class="px-3 py-1.5 bg-input-background border border-input-border font-medium rounded-lg flex items-center space-x-2">
class="px-2 py-1 bg-input-background border border-input-border font-medium rounded-lg flex items-center space-x-2">
<CalendarIcon class="w-5"></CalendarIcon>
<div class="text-white">
{{ formatDate(start) }}

View File

@@ -85,7 +85,7 @@ const { floatingStyles } = useFloating(reference, floating, {
<Teleport to="body">
<div
v-show="open"
class="fixed inset-0 z-40"
class="fixed inset-0 z-50"
@click.prevent="onBackgroundClick" />
<transition
enter-active-class="transition-opacity ease-out duration-200"

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
const value = defineModel();
const emit = defineEmits(['changed']);
@@ -12,6 +12,13 @@ function onChange(event: Event) {
}
}
watch(
() => value.value,
(newValue) => {
liveDataValue.value = newValue;
}
);
function onInput(event: Event) {
liveDataValue.value = (event.target as HTMLInputElement).value;
}

View File

@@ -27,7 +27,7 @@ const open = ref(false);
<template #trigger>
<button
data-testid="time_entry_range_selector"
class="text-muted w-[110px] px-2 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border"
class="text-muted w-[110px] px-2 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80"
:class="{
'text-sm py-2 font-medium': !showDate,
'text-xs py-1.5 font-semibold': showDate,

View File

@@ -16,9 +16,9 @@ import TimeEntryDescriptionInput from '@/packages/ui/src/TimeEntry/TimeEntryDesc
import TimeEntryRowTagDropdown from '@/packages/ui/src/TimeEntry/TimeEntryRowTagDropdown.vue';
import TimeEntryRowDurationInput from '@/packages/ui/src/TimeEntry/TimeEntryRowDurationInput.vue';
import TimeEntryMoreOptionsDropdown from '@/packages/ui/src/TimeEntry/TimeEntryMoreOptionsDropdown.vue';
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
import BillableToggleButton from '@/packages/ui/src/Input/BillableToggleButton.vue';
import { computed } from 'vue';
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
const props = defineProps<{
timeEntry: TimeEntry;
@@ -37,8 +37,11 @@ const props = defineProps<{
currency: string;
showMember?: boolean;
showDate?: boolean;
selected?: boolean;
}>();
const emit = defineEmits<{ selected: []; unselected: [] }>();
function updateTimeEntryDescription(description: string) {
props.updateTimeEntry({ ...props.timeEntry, description });
}
@@ -74,6 +77,15 @@ const memberName = computed(() => {
}
return '';
});
function onSelectChange(event: Event) {
const target = event.target as HTMLInputElement;
if (target.checked) {
emit('selected');
} else {
emit('unselected');
}
}
</script>
<template>
@@ -85,6 +97,8 @@ const memberName = computed(() => {
class="sm:flex py-0.5 min-w-0 items-center justify-between group">
<div class="flex space-x-1 items-center min-w-0">
<input
@change="onSelectChange"
:value="selected"
type="checkbox"
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:ring-accent-500/80" />
<div class="w-7 h-7" v-if="indent === true"></div>

View File

@@ -520,6 +520,7 @@ const showCreateProject = ref(false);
<Badge
@click="showCreateProject = true"
size="large"
tag="button"
class="cursor-pointer hover:bg-tertiary">
<PlusIcon class="-ml-1 w-5"></PlusIcon>
<span>Add new project</span>