diff --git a/app/src/plugins/tm@_components/z-datepicker.vue b/app/src/plugins/tm@_components/z-datepicker.vue index 039efd1..96aaeb9 100644 --- a/app/src/plugins/tm@_components/z-datepicker.vue +++ b/app/src/plugins/tm@_components/z-datepicker.vue @@ -1,7 +1,8 @@ {{ label }} @@ -13,6 +14,7 @@ @closed="handleClose" v-bind="$attrs" :placeholder="hasValue ? ' ' : placeholder" + :uid="uniqueId" /> @@ -46,10 +48,7 @@ export default { data() { return { - isFocused: false, - preventInputFocus: false, - savedScrollPosition: null, - focusListeners: new Map(), + uniqueId: `z-datepicker-${crypto.randomUUID()}`, }; }, @@ -59,74 +58,26 @@ export default { }, }, - mounted() { - // Attach focus listeners on mount to catch any focus events - // Use immediate attachment (no $nextTick) to catch early focus events - this.attachFocusListeners(); - }, - methods: { handleUpdate(value) { this.$emit('update:modelValue', value); }, handleOpen(event) { - this.isFocused = true; - - // IMPORTANT: If scroll position wasn't saved yet (handleClick wasn't called), - // save it now but it might be too late and already scrolled to 0 - if (this.savedScrollPosition === null) { - this.preventInputFocus = true; - const modalContent = document.querySelector('.v-dialog .modal-content'); - if (modalContent) { - this.savedScrollPosition = modalContent.scrollTop; - } - } - - // CRITICAL: Blur any currently focused input (v-dialog autofocus issue) - this.blurFocusedInputs(); - - // Reattach focus event listeners (for dynamically added inputs) - this.attachFocusListeners(); - - // Position the calendar menu relative to the modal - this.positionCalendar(); - - // Reset after datepicker is opened - setTimeout(() => { - this.preventInputFocus = false; - this.savedScrollPosition = null; - }, 500); - - // Emit to parent (for backward compatibility) - const wrapperElement = - event?.target?.closest?.('.z-datepicker-wrapper') || true; - this.$emit('open', wrapperElement); + this.$emit('open', event); }, handleClose(event) { - this.isFocused = false; this.$emit('closed', event); }, handleClick(event) { - // Prevent any default behavior - event.preventDefault(); - event.stopPropagation(); - - // CRITICAL: Set prevention flag BEFORE opening menu to catch early focus events - this.preventInputFocus = true; - - // Save scroll position FIRST (before blur or opening menu) - const modalContent = document.querySelector('.v-dialog .modal-content'); - if (modalContent) { - this.savedScrollPosition = modalContent.scrollTop; + // Only handle clicks on the wrapper itself, not on the datepicker's internal clear button + if (event.target.closest('.dp__clear_icon')) { + return; // Let the clear button work normally } - // CRITICAL: Blur any currently focused input - this.blurFocusedInputs(false); // Pass false to not restore scroll yet - - // Open the menu + // Open the menu programmatically if (this.$refs.datepickerRef?.openMenu) { this.$refs.datepickerRef.openMenu(); } @@ -137,129 +88,6 @@ export default { this.$refs.datepickerRef.openMenu(); } }, - - positionCalendar() { - setTimeout(() => { - const calendar = document.querySelector( - '.dp__menu.dp__menu_index.dp__theme_light', - ); - - if (!calendar) return; - - // Get the datepicker wrapper element - const datepickerWrapper = this.$el; - - // Get the modal container - const modalContainer = document.querySelector('.v-overlay__content'); - - if (!datepickerWrapper || !modalContainer) return; - - // Get bounding rectangles - const wrapperRect = datepickerWrapper.getBoundingClientRect(); - const modalRect = modalContainer.getBoundingClientRect(); - - // Calculate position relative to modal - const topPosition = - wrapperRect.top - modalRect.top + wrapperRect.height; - const leftPosition = wrapperRect.left - modalRect.left; - - // Apply positioning - calendar.style.top = `${topPosition + 210}px`; - }, 100); - }, - - blurFocusedInputs(restoreScroll = true) { - const activeElement = document.activeElement; - - if (activeElement && activeElement.tagName === 'INPUT') { - // Don't blur the datepicker's own input - if ( - activeElement.closest('.dp__input_wrap') || - activeElement.closest('.z-datepicker-wrapper') - ) { - return; - } - - activeElement.blur(); - - // Restore scroll position after blur (only if requested) - if (restoreScroll && this.savedScrollPosition !== null) { - const modalContent = document.querySelector( - '.v-dialog .modal-content', - ); - if (modalContent) { - modalContent.scrollTop = this.savedScrollPosition; - } - } - } - }, - - attachFocusListeners() { - // Remove old listeners first - this.removeFocusListeners(); - - // Attach new listeners to autocomplete inputs only (like the mixin) - // Trying to catch all inputs causes issues with event timing - const inputs = document.querySelectorAll('.v-autocomplete input'); - - let totalListeners = 0; - inputs.forEach((input) => { - // Skip the datepicker's own input - if (input.closest('.dp__input_wrap')) { - return; - } - - const listener = this.onInputFocus.bind(this); - this.focusListeners.set(input, listener); - input.addEventListener('focus', listener); - totalListeners++; - }); - }, - - onInputFocus(event) { - const target = event.target; - - // Skip if it's the datepicker's own input - if ( - target.closest('.dp__input_wrap') || - target.closest('.z-datepicker-wrapper') - ) { - return; - } - - // If prevention flag is set, block the focus - if (this.preventInputFocus && this.savedScrollPosition !== null) { - const scrollBefore = document.querySelector( - '.v-dialog .modal-content', - )?.scrollTop; - - // Blur the focused element with a slight delay (like the mixin does) - setTimeout(() => { - target.blur(); - - // Restore scroll position after blur - const modalContent = document.querySelector( - '.v-dialog .modal-content', - ); - if (modalContent && this.savedScrollPosition !== null) { - modalContent.scrollTop = this.savedScrollPosition; - const scrollAfter = modalContent.scrollTop; - } - }, 0); - } - }, - - removeFocusListeners() { - this.focusListeners.forEach((listener, input) => { - input.removeEventListener('focus', listener); - }); - this.focusListeners.clear(); - }, - }, - - beforeUnmount() { - // Cleanup listeners on component unmount - this.removeFocusListeners(); }, }; @@ -294,11 +122,6 @@ export default { background: white padding: 0 4px left: 13px - - &.is-focused .z-datepicker-label - color: rgba(0, 0, 0, 0.87) - - &.has-value .z-datepicker-label color: rgba(0, 0, 0, 0.6) &.has-value :deep(.dp__input_icon) @@ -313,19 +136,21 @@ export default { padding-bottom: 14px !important :deep(.dp__input_wrap) - pointer-events: none cursor: pointer :deep(.dp__input) cursor: pointer pointer-events: none !important + user-select: none :deep(.dp__clear_icon) pointer-events: auto !important cursor: pointer !important top: 28px !important + z-index: 10 :deep(.dp__input_icon) top: 27px !important left: 3px !important + pointer-events: none !important diff --git a/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts b/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts deleted file mode 100644 index 8d46da1..0000000 --- a/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts +++ /dev/null @@ -1,72 +0,0 @@ -export default { - // Mixin to fix autofocus on v-input in modals where date selects are used - data() { - return { - preventInputFocus: false, - savedScrollPosition: null, - }; - }, - - methods: { - setFocusEvent() { - const inputs = document.querySelectorAll('.v-autocomplete input'); - inputs.forEach((input) => { - input.addEventListener('focus', this.onInputFocus); - }); - }, - - onDateFocus($event) { - if ($event == undefined) { - this.preventInputFocus = true; - } - }, - - onDateOpen($event) { - // $event should be the element reference (or any truthy value to trigger saving) - if ($event) { - // Find the scrollable modal container - const modalContent = document.querySelector('.v-dialog .modal-content'); - if (modalContent) { - this.savedScrollPosition = modalContent.scrollTop; - } - - this.preventInputFocus = true; - - // Reset after datepicker is opened - setTimeout(() => { - this.preventInputFocus = false; - this.savedScrollPosition = null; - }, 500); - } - }, - - onInputFocus($event) { - const target = $event.target; - - // If we have a saved scroll position and an unwanted focus happened - if (this.preventInputFocus && this.savedScrollPosition !== null) { - const target = $event.target; - - // Only intercept text input focus (autocomplete fields) - if (target.tagName === 'INPUT' && target.type === 'text') { - const scrollBefore = document.querySelector( - '.v-dialog .modal-content', - )?.scrollTop; - - // Blur the focused element - setTimeout(() => { - target.blur(); - - const modalContent = document.querySelector( - '.v-dialog .modal-content', - ); - if (modalContent && this.savedScrollPosition !== null) { - modalContent.scrollTop = this.savedScrollPosition; - const scrollAfter = modalContent.scrollTop; - } - }, 0); - } - } - }, - }, -}; diff --git a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue index 9b04962..71cf9d6 100644 --- a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue +++ b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue @@ -1,197 +1,278 @@ - - - - - {{ blocker.id ? "Upraviť" : "Vytvoriť" }} blocker - - + + + + + {{ blocker.id ? 'Upraviť' : 'Vytvoriť' }} blocker + + - - + + - - - + + + - - - + + + - {{ blocker.id ? "UPRAVIŤ" : "VYTVORIŤ" }} - - - + {{ + blocker.id ? 'UPRAVIŤ' : 'VYTVORIŤ' + }} + + + diff --git a/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue b/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue index 4eb39af..d0badd6 100644 --- a/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue +++ b/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue @@ -1,5 +1,5 @@ - + Ste si istý, že chcete zmazať {{ blocker.name }}? diff --git a/app/src/plugins/tm@modal/master-task/z-complete-master-task-modal.vue b/app/src/plugins/tm@modal/master-task/z-complete-master-task-modal.vue index 4b05c93..13b49e2 100644 --- a/app/src/plugins/tm@modal/master-task/z-complete-master-task-modal.vue +++ b/app/src/plugins/tm@modal/master-task/z-complete-master-task-modal.vue @@ -1,5 +1,5 @@ - + Ste si istý, že chcete {{ mastertask.is_finished ? 'znovu otvoriť' : 'dokončiť' }} {{ mastertask?.name }}? diff --git a/app/src/plugins/tm@modal/master-task/z-delete-master-task-modal.vue b/app/src/plugins/tm@modal/master-task/z-delete-master-task-modal.vue index b21a332..63daf76 100644 --- a/app/src/plugins/tm@modal/master-task/z-delete-master-task-modal.vue +++ b/app/src/plugins/tm@modal/master-task/z-delete-master-task-modal.vue @@ -1,5 +1,5 @@ - + Ste si istý, že chcete zmazať {{ mastertask.name }}? diff --git a/app/src/plugins/tm@modal/master-task/z-delete-team-member-modal.vue b/app/src/plugins/tm@modal/master-task/z-delete-team-member-modal.vue index 06581e6..2155465 100644 --- a/app/src/plugins/tm@modal/master-task/z-delete-team-member-modal.vue +++ b/app/src/plugins/tm@modal/master-task/z-delete-team-member-modal.vue @@ -1,5 +1,5 @@ - + Chcete túto osobu odstrániť z tímu? diff --git a/app/src/plugins/tm@modal/master-task/z-master-task-modal.vue b/app/src/plugins/tm@modal/master-task/z-master-task-modal.vue index 093018e..01d403b 100644 --- a/app/src/plugins/tm@modal/master-task/z-master-task-modal.vue +++ b/app/src/plugins/tm@modal/master-task/z-master-task-modal.vue @@ -5,6 +5,7 @@ max-height="90%" height="700px" persistent + :retain-focus="false" > @@ -83,7 +82,6 @@ label="Čas" append-inner-icon="mdi-clock-outline" type="time" - @focus="onInputFocus" :class="{ error: v$.mastertask._deadline_time.$errors.length }" :hint=" v$.mastertask._deadline_time.$errors @@ -95,7 +93,6 @@ { - this.preventInputFocus = false; - this.savedScrollPosition = null; - }, 500); - } - }, - closeBothModals() { this.$emit('close'); this.visible = false; @@ -553,7 +520,6 @@ export default { this.visible = true; this.mastertask = null; this.$nextTick(() => { - this.setFocusEvent(); this.v$.$reset(); // Reset after modal is fully rendered and v-dialog autofocus has been blocked diff --git a/app/src/plugins/tm@modal/task-cell/z-show-menu-modal.vue b/app/src/plugins/tm@modal/task-cell/z-show-menu-modal.vue index 2c69a44..b3ee692 100644 --- a/app/src/plugins/tm@modal/task-cell/z-show-menu-modal.vue +++ b/app/src/plugins/tm@modal/task-cell/z-show-menu-modal.vue @@ -1,5 +1,5 @@ - + Vytvoriť diff --git a/app/src/plugins/tm@modal/task/_components/_components/a-task-spent-time.vue b/app/src/plugins/tm@modal/task/_components/_components/a-task-spent-time.vue index 34726a6..408069d 100644 --- a/app/src/plugins/tm@modal/task/_components/_components/a-task-spent-time.vue +++ b/app/src/plugins/tm@modal/task/_components/_components/a-task-spent-time.vue @@ -51,7 +51,7 @@ - + Pridať odpracovaný čas diff --git a/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue b/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue index f41da5e..47306c1 100644 --- a/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue +++ b/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue @@ -58,7 +58,7 @@ - + Pridať dátum @@ -70,8 +70,9 @@ - Potvrdiť - + @@ -152,7 +153,7 @@ import tmUtils from '@/plugins/tm@_utils/tm.utils'; import errorUtils from '@/plugins/lib@auth/_utils/error.utils'; import useVuelidate from '@vuelidate/core'; import { required } from '@vuelidate/validators'; -import Datepicker from '@vuepic/vue-datepicker'; +import ZDatepicker from '@/plugins/tm@_components/z-datepicker.vue'; import moment from 'moment'; import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'; import { mapGetters } from 'vuex'; @@ -162,7 +163,7 @@ export default { emits: ['update-subtask'], components: { - Datepicker, + ZDatepicker, }, props: { diff --git a/app/src/plugins/tm@modal/task/_components/a-timeblock.vue b/app/src/plugins/tm@modal/task/_components/a-timeblock.vue index 659671c..a89aa50 100644 --- a/app/src/plugins/tm@modal/task/_components/a-timeblock.vue +++ b/app/src/plugins/tm@modal/task/_components/a-timeblock.vue @@ -3,15 +3,11 @@ x - - + import useVuelidate from '@vuelidate/core'; import { requiredIf } from '@vuelidate/validators'; -import Datepicker from '@vuepic/vue-datepicker'; +import ZDatepicker from '@/plugins/tm@_components/z-datepicker.vue'; export default { components: { - Datepicker, + ZDatepicker, }, props: { @@ -71,21 +67,6 @@ export default { default: false, }, - preventInputFocus: { - type: Boolean, - default: false, - }, - - onInputFocus: { - type: Function, - default: () => ({}), - }, - - onDateOpen: { - type: Function, - default: () => ({}), - }, - index: { type: Number, }, @@ -107,50 +88,6 @@ export default { }, methods: { - handleDatepickerClick() { - // Pass the datepicker wrapper element reference to the mixin - const scrollTarget = - this.$refs[`timeblock-datepicker-wrapper-${this.index}`]; - this.onDateOpen(scrollTarget); - // Open the datepicker - this.openDatepicker(`timeblock-datepicker-${this.index}`); - - setTimeout(() => { - const calendar = document.querySelector( - '.dp__menu.dp__menu_index.dp__theme_light', - ); - - if (!calendar) return; - - // Get the datepicker wrapper element - const datepickerWrapper = - this.$refs[`timeblock-datepicker-wrapper-${this.index}`]; - - // Get the modal container - const modalContainer = document.querySelector('.v-overlay__content'); - - if (!datepickerWrapper || !modalContainer) return; - - // Get bounding rectangles - const wrapperRect = datepickerWrapper.getBoundingClientRect(); - const modalRect = modalContainer.getBoundingClientRect(); - - // Calculate position relative to modal - const topPosition = - wrapperRect.top - modalRect.top + wrapperRect.height; - const leftPosition = wrapperRect.left - modalRect.left; - - // Apply positioning - // calendar.style.position = 'absolute'; - calendar.style.top = `${topPosition + 210}px`; - // calendar.style.left = `${leftPosition}px`; - }, 100); - }, - - async openDatepicker(ref) { - this.$refs[ref].openMenu(); - }, - updateDate(date) { const updated = { ...this.timeblock, date }; this.$emit('updateTimeblock', updated); @@ -195,7 +132,4 @@ export default { .datepicker-wrapper width: 100% - .datepicker-input - width: 100% !important - pointer-events: none diff --git a/app/src/plugins/tm@modal/task/z-complete-task-modal.vue b/app/src/plugins/tm@modal/task/z-complete-task-modal.vue index b8fefa4..348fa79 100644 --- a/app/src/plugins/tm@modal/task/z-complete-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-complete-task-modal.vue @@ -1,5 +1,5 @@ - + diff --git a/app/src/plugins/tm@modal/task/z-delete-task-modal.vue b/app/src/plugins/tm@modal/task/z-delete-task-modal.vue index ed2ba25..160b48d 100644 --- a/app/src/plugins/tm@modal/task/z-delete-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-delete-task-modal.vue @@ -1,5 +1,5 @@ - + Ste si istý, že chcete zmazať sub task z mastertasku {{ subtask.project_code }}? diff --git a/app/src/plugins/tm@modal/task/z-delete-timeblock-modal.vue b/app/src/plugins/tm@modal/task/z-delete-timeblock-modal.vue index 32f7505..f894df4 100644 --- a/app/src/plugins/tm@modal/task/z-delete-timeblock-modal.vue +++ b/app/src/plugins/tm@modal/task/z-delete-timeblock-modal.vue @@ -1,5 +1,5 @@ - + Vyberte časový úsek, ktorý chcete odstrániť diff --git a/app/src/plugins/tm@modal/task/z-task-add-time-modal.vue b/app/src/plugins/tm@modal/task/z-task-add-time-modal.vue index c0e676a..456dc3e 100644 --- a/app/src/plugins/tm@modal/task/z-task-add-time-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-add-time-modal.vue @@ -1,5 +1,5 @@ - + Pridať odpracovaný čas diff --git a/app/src/plugins/tm@modal/task/z-task-modal.vue b/app/src/plugins/tm@modal/task/z-task-modal.vue index 44836f6..4c49f4f 100644 --- a/app/src/plugins/tm@modal/task/z-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-modal.vue @@ -5,6 +5,7 @@ max-height="90%" height="700px" persistent + :retain-focus="false" > @@ -352,7 +343,6 @@ import useVuelidate from '@vuelidate/core'; import { required, requiredIf, optional } from '@vuelidate/validators'; import errorUtils from '@/plugins/lib@auth/_utils/error.utils'; import { QuillEditor } from '@vueup/vue-quill'; -import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus'; import SelectsMixin from '../master-task/_mixins/selects.mixin'; import * as FileSaver from 'file-saver'; import tmUtils from '@/plugins/tm@_utils/tm.utils'; @@ -367,7 +357,7 @@ import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'; export default { emits: ['close', 'refresh'], - mixins: [VuetifyFixFocusMixin, SelectsMixin, AutocompleteMixin, modalMixin], + mixins: [SelectsMixin, AutocompleteMixin, modalMixin], components: { ZTaskReadonlyInfo, @@ -503,29 +493,6 @@ export default { }, methods: { - // Override onDateOpen from VuetifyFixFocusMixin to add logging - onDateOpen($event) { - // Call the parent mixin's onDateOpen method - if ( - VuetifyFixFocusMixin.methods && - VuetifyFixFocusMixin.methods.onDateOpen - ) { - VuetifyFixFocusMixin.methods.onDateOpen.call(this, $event); - } - }, - - // Override onInputFocus from VuetifyFixFocusMixin to add logging - onInputFocus($event) { - const target = $event?.target; - // Call the parent mixin's onInputFocus method - if ( - VuetifyFixFocusMixin.methods && - VuetifyFixFocusMixin.methods.onInputFocus - ) { - VuetifyFixFocusMixin.methods.onInputFocus.call(this, $event); - } - }, - toggleEditMode() { if (this.subtask.type == 'office365') return this.$events.emit('show-unable-to-edit-office-task-modal', { @@ -595,7 +562,6 @@ export default { ]; this.$nextTick(() => { this.v$.$reset(); - this.setFocusEvent(); // Reset after modal is fully rendered and v-dialog autofocus has been blocked setTimeout(() => { diff --git a/app/src/plugins/tm@modal/task/z-unable-to-complete-task-modal.vue b/app/src/plugins/tm@modal/task/z-unable-to-complete-task-modal.vue index 88b7a33..575012f 100644 --- a/app/src/plugins/tm@modal/task/z-unable-to-complete-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-unable-to-complete-task-modal.vue @@ -1,5 +1,5 @@ - + Sub Task má nedokončené úlohy diff --git a/app/src/plugins/tm@modal/task/z-unable-to-edit-office-task-modal.vue b/app/src/plugins/tm@modal/task/z-unable-to-edit-office-task-modal.vue index 910bbb4..e5a5f21 100644 --- a/app/src/plugins/tm@modal/task/z-unable-to-edit-office-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-unable-to-edit-office-task-modal.vue @@ -1,5 +1,5 @@ - + Sub Task zosynchronizovaný z Office365 sa nedá upravovat. Upravte ho vo svojom kalendári a pockajte na zosynchronizovanie s Task Managerom diff --git a/app/src/plugins/tm@modal/team/z-delete-team-modal.vue b/app/src/plugins/tm@modal/team/z-delete-team-modal.vue index 0cc83d3..fe90800 100644 --- a/app/src/plugins/tm@modal/team/z-delete-team-modal.vue +++ b/app/src/plugins/tm@modal/team/z-delete-team-modal.vue @@ -1,5 +1,5 @@ - + diff --git a/app/src/plugins/tm@modal/team/z-team-modal.vue b/app/src/plugins/tm@modal/team/z-team-modal.vue index e5855ed..a66e615 100644 --- a/app/src/plugins/tm@modal/team/z-team-modal.vue +++ b/app/src/plugins/tm@modal/team/z-team-modal.vue @@ -1,5 +1,5 @@ - +