From d5699da234a365148084a2f5421b0743a44cf647 Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Mon, 11 Nov 2024 00:52:56 +0100 Subject: [PATCH] improve manual time entry modal, improve time picker, add human duration input --- resources/css/app.css | 2 +- .../Common/TimeEntry/TimeEntryCreateModal.vue | 154 ++++++++++++---- .../Partials/TwoFactorAuthenticationForm.vue | 2 +- resources/js/Pages/Time.vue | 3 + .../ui/src/Buttons/SecondaryButton.vue | 4 +- .../js/packages/ui/src/Input/DatePicker.vue | 3 +- .../ui/src/Input/DurationHumanInput.vue | 102 ++++++++++ .../packages/ui/src/Input/SelectDropdown.vue | 34 +++- .../js/packages/ui/src/Input/TimePicker.vue | 174 +++++++++--------- .../TimeTrackerProjectTaskDropdown.vue | 9 +- resources/js/utils/useCurrentTimeEntry.ts | 2 +- 11 files changed, 355 insertions(+), 134 deletions(-) create mode 100644 resources/js/packages/ui/src/Input/DurationHumanInput.vue diff --git a/resources/css/app.css b/resources/css/app.css index 5b97f0e6..bf927fdb 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -57,7 +57,7 @@ } /* Track */ -::-webkit-scrollbar-track { +::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; } diff --git a/resources/js/Components/Common/TimeEntry/TimeEntryCreateModal.vue b/resources/js/Components/Common/TimeEntry/TimeEntryCreateModal.vue index dead653f..54019f48 100644 --- a/resources/js/Components/Common/TimeEntry/TimeEntryCreateModal.vue +++ b/resources/js/Components/Common/TimeEntry/TimeEntryCreateModal.vue @@ -2,15 +2,12 @@ import TextInput from '@/packages/ui/src/Input/TextInput.vue'; import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue'; import DialogModal from '@/packages/ui/src/DialogModal.vue'; -import { nextTick, ref, watch } from 'vue'; +import { computed, nextTick, ref, watch } from 'vue'; import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue'; -import TimeTrackerTagDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerTagDropdown.vue'; import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue'; -import BillableToggleButton from '@/packages/ui/src/Input/BillableToggleButton.vue'; import { getCurrentUserId } from '@/utils/useUser'; -import { useTimeEntriesStore } from '@/utils/useTimeEntries'; import InputLabel from '@/packages/ui/src/Input/InputLabel.vue'; -import DatePicker from '@/packages/ui/src/Input/DatePicker.vue'; +import { TagIcon } from '@heroicons/vue/20/solid'; import { getDayJsInstance, getLocalizedDayJs, @@ -24,11 +21,20 @@ import type { CreateProjectBody, Project, Client, + CreateTimeEntryBody, } from '@/packages/api/src'; import { useClientsStore } from '@/utils/useClients'; import TimePicker from '@/packages/ui/src/Input/TimePicker.vue'; import { getOrganizationCurrencyString } from '@/utils/money'; import { canCreateProjects } from '@/utils/permissions'; +import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue'; +import { Badge } from '@/packages/ui/src'; +import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue'; +import SelectDropdown from '../../../packages/ui/src/Input/SelectDropdown.vue'; +import DatePicker from '@/packages/ui/src/Input/DatePicker.vue'; +import DurationHumanInput from '@/packages/ui/src/Input/DurationHumanInput.vue'; + +import { InformationCircleIcon } from '@heroicons/vue/20/solid'; const projectStore = useProjectsStore(); const { projects } = storeToRefs(projectStore); const taskStore = useTasksStore(); @@ -36,26 +42,18 @@ const { tasks } = storeToRefs(taskStore); const clientStore = useClientsStore(); const { clients } = storeToRefs(clientStore); -const { createTimeEntry } = useTimeEntriesStore(); const show = defineModel('show', { default: false }); const saving = ref(false); -defineProps<{ +const props = defineProps<{ enableEstimatedTime: boolean; + createTimeEntry: ( + entry: Omit + ) => Promise; + createClient: (client: CreateClientBody) => Promise; + createProject: (project: CreateProjectBody) => Promise; }>(); -async function createProject( - project: CreateProjectBody -): Promise { - return await useProjectsStore().createProject(project); -} - -async function createClient( - body: CreateClientBody -): Promise { - return await useClientsStore().createClient(body); -} - const description = ref(null); watch(show, (value) => { @@ -72,7 +70,7 @@ const timeEntryDefaultValues = { task_id: null, tags: [], billable: false, - start: getDayJsInstance().utc().format(), + start: getDayJsInstance().utc().subtract(1, 'h').format(), end: getDayJsInstance().utc().format(), user_id: getCurrentUserId(), }; @@ -97,7 +95,7 @@ watch(localEnd, (value) => { }); async function submit() { - await createTimeEntry(timeEntry.value); + await props.createTimeEntry({ ...timeEntry.value }); timeEntry.value = { ...timeEntryDefaultValues }; localStart.value = getLocalizedDayJs(timeEntryDefaultValues.start).format(); localEnd.value = getLocalizedDayJs(timeEntryDefaultValues.end).format(); @@ -107,6 +105,13 @@ const { tags } = storeToRefs(useTagsStore()); async function createTag(tag: string) { return await useTagsStore().createTag(tag); } + +const billableProxy = computed({ + get: () => (timeEntry.value.billable ? 'true' : 'false'), + set: (value: string) => { + timeEntry.value.billable = value === 'true'; + }, +});