Skip to content

Commit 0653c22

Browse files
authored
Merge pull request #1225 from shariquerik/calendar-fixes
2 parents 509bdd0 + af69705 commit 0653c22

File tree

7 files changed

+143
-148
lines changed

7 files changed

+143
-148
lines changed

frontend/src/components/Calendar/Attendee.vue

Lines changed: 105 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,70 @@
11
<template>
22
<div>
3-
<div
4-
class="flex items-center justify-between text-ink-gray-7 [&>div]:w-full"
5-
>
6-
<Popover v-model:show="showOptions">
7-
<template #target="{ togglePopover }">
8-
<TextInput
3+
<!-- Combobox Input -->
4+
<div class="flex items-center w-full text-ink-gray-8 [&>div]:w-full">
5+
<ComboboxRoot
6+
:model-value="tempSelection"
7+
:open="showOptions"
8+
@update:open="(o) => (showOptions = o)"
9+
@update:modelValue="onSelect"
10+
:ignore-filter="true"
11+
>
12+
<ComboboxAnchor
13+
class="flex w-full text-base items-center gap-1 rounded border border-outline-gray-2 bg-surface-white hover:border-outline-gray-3 focus:border-outline-gray-4 focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 px-2 py-1"
14+
:class="[size === 'sm' ? 'h-7' : 'h-8 ', inputClass]"
15+
@click="showOptions = true"
16+
>
17+
<ComboboxInput
918
ref="search"
10-
type="text"
11-
:size="size"
12-
class="w-full"
13-
variant="outline"
14-
v-model="query"
15-
:debounce="300"
19+
autocomplete="off"
20+
class="bg-transparent p-0 outline-none border-0 text-base text-ink-gray-8 h-full placeholder:text-ink-gray-4 w-full focus:outline-none focus:ring-0 focus:border-0"
1621
:placeholder="placeholder"
17-
@click="togglePopover"
18-
@keydown="onKeydown"
22+
:value="query"
23+
@input="onInput"
24+
@keydown.enter.prevent="handleEnter"
25+
@keydown.escape.stop="showOptions = false"
26+
/>
27+
<FeatherIcon
28+
name="chevron-down"
29+
class="h-4 text-ink-gray-5 cursor-pointer"
30+
@click.stop="showOptions = !showOptions"
31+
/>
32+
</ComboboxAnchor>
33+
<ComboboxPortal>
34+
<ComboboxContent
35+
class="z-10 mt-1 min-w-48 w-full max-w-md bg-surface-modal overflow-hidden rounded-lg shadow-2xl ring-1 ring-black ring-opacity-5"
36+
position="popper"
37+
:align="'start'"
38+
@openAutoFocus.prevent
39+
@closeAutoFocus.prevent
1940
>
20-
<template #suffix>
21-
<FeatherIcon
22-
name="chevron-down"
23-
class="h-4 text-ink-gray-5"
24-
@click.stop="togglePopover()"
25-
/>
26-
</template>
27-
</TextInput>
28-
</template>
29-
<template #body="{ isOpen }">
30-
<div v-show="isOpen">
31-
<div
32-
class="mt-1 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
33-
>
34-
<ul
35-
v-if="options.length"
36-
role="listbox"
37-
class="p-1.5 max-h-[12rem] overflow-y-auto"
38-
>
39-
<li
40-
v-for="(option, idx) in options"
41-
:key="option.value"
42-
role="option"
43-
:aria-selected="idx === highlightIndex"
44-
@click="selectOption(option)"
45-
@mouseenter="highlightIndex = idx"
46-
class="flex cursor-pointer items-center rounded px-2 py-1 text-base"
47-
:class="{ 'bg-surface-gray-3': idx === highlightIndex }"
48-
>
49-
<UserAvatar class="mr-2" :user="option.value" size="lg" />
50-
<div class="flex flex-col gap-1 p-1 text-ink-gray-8">
51-
<div class="text-base font-medium">
52-
{{ option.label }}
53-
</div>
54-
<div class="text-sm text-ink-gray-5">
55-
{{ option.value }}
56-
</div>
57-
</div>
58-
</li>
59-
</ul>
60-
<div
61-
v-else
41+
<ComboboxViewport class="max-h-60 overflow-auto p-1.5">
42+
<ComboboxEmpty
6243
class="flex gap-2 rounded px-2 py-1 text-base text-ink-gray-5"
6344
>
6445
<FeatherIcon v-if="fetchContacts" name="search" class="h-4" />
65-
{{
66-
fetchContacts
67-
? __('No results found')
68-
: __('Type an email address to add attendee')
69-
}}
70-
</div>
71-
</div>
72-
</div>
73-
</template>
74-
</Popover>
46+
{{ emptyStateText }}
47+
</ComboboxEmpty>
48+
<ComboboxItem
49+
v-for="option in options"
50+
:key="option.value"
51+
:value="option.value"
52+
class="text-base leading-none text-ink-gray-7 rounded flex items-center px-2 py-1 relative select-none data-[highlighted]:outline-none data-[highlighted]:bg-surface-gray-3 cursor-pointer"
53+
@mousedown.prevent="onSelect(option.value, option)"
54+
>
55+
<UserAvatar class="mr-2" :user="option.value" size="lg" />
56+
<div class="flex flex-col gap-1 p-1 text-ink-gray-8">
57+
<div class="text-base font-medium">{{ option.label }}</div>
58+
<div class="text-sm text-ink-gray-5">{{ option.value }}</div>
59+
</div>
60+
</ComboboxItem>
61+
</ComboboxViewport>
62+
</ComboboxContent>
63+
</ComboboxPortal>
64+
</ComboboxRoot>
7565
</div>
66+
67+
<!-- Selected Attendees -->
7668
<div
7769
v-if="values.length"
7870
class="flex flex-col gap-2 mt-2 max-h-[165px] overflow-y-auto"
@@ -105,8 +97,18 @@
10597

10698
<script setup>
10799
import UserAvatar from '@/components/UserAvatar.vue'
108-
import { createResource, TextInput, Popover } from 'frappe-ui'
109-
import { ref, computed, nextTick, watch } from 'vue'
100+
import { createResource } from 'frappe-ui'
101+
import {
102+
ComboboxRoot,
103+
ComboboxAnchor,
104+
ComboboxInput,
105+
ComboboxPortal,
106+
ComboboxContent,
107+
ComboboxViewport,
108+
ComboboxItem,
109+
ComboboxEmpty,
110+
} from 'reka-ui'
111+
import { ref, computed, nextTick } from 'vue'
110112
import { watchDebounced } from '@vueuse/core'
111113
112114
const props = defineProps({
@@ -154,7 +156,7 @@ const query = ref('')
154156
const text = ref('')
155157
const showOptions = ref(false)
156158
const optionsRef = ref(null)
157-
const highlightIndex = ref(-1)
159+
const tempSelection = ref(null)
158160
159161
const metaByEmail = computed(() => {
160162
const out = {}
@@ -225,6 +227,12 @@ const options = computed(() => {
225227
return searchedContacts || []
226228
})
227229
230+
const emptyStateText = computed(() =>
231+
props.fetchContacts
232+
? __('No results found')
233+
: __('Type an email address to add attendee'),
234+
)
235+
228236
function reload(val) {
229237
if (!props.fetchContacts) return
230238
@@ -234,34 +242,38 @@ function reload(val) {
234242
filterOptions.reload()
235243
}
236244
237-
watch(
238-
() => options.value,
239-
() => {
240-
highlightIndex.value = options.value.length ? 0 : -1
241-
},
242-
)
243-
244-
function selectOption(option) {
245-
if (!option) return
246-
addValue(option)
247-
!error.value && (query.value = '')
248-
showOptions.value = false
249-
}
250-
251-
function onKeydown(e) {
252-
if (e.key === 'Enter') {
253-
if (highlightIndex.value >= 0 && options.value[highlightIndex.value]) {
254-
selectOption(options.value[highlightIndex.value])
255-
} else if (query.value) {
256-
// Add entered email directly
257-
selectOption({ name: 'new', label: query.value, value: query.value })
245+
function onSelect(val, fullOption = null) {
246+
if (!val) return
247+
const optionObj = fullOption ||
248+
options.value.find((o) => o.value === val) || {
249+
name: 'new',
250+
label: val,
251+
value: val,
258252
}
259-
e.preventDefault()
260-
} else if (e.key === 'Escape') {
253+
addValue(optionObj)
254+
if (!error.value) {
255+
query.value = ''
256+
tempSelection.value = null
261257
showOptions.value = false
258+
nextTick(() => setFocus())
259+
}
260+
}
261+
262+
function handleEnter() {
263+
if (query.value) {
264+
onSelect(query.value, {
265+
name: 'new',
266+
label: query.value,
267+
value: query.value,
268+
})
262269
}
263270
}
264271
272+
function onInput(e) {
273+
query.value = e.target.value
274+
showOptions.value = true
275+
}
276+
265277
const addValue = (option) => {
266278
// Safeguard for falsy option
267279
if (!option || !option.value) return
@@ -315,7 +327,7 @@ const removeValue = (email) => {
315327
}
316328
317329
function setFocus() {
318-
search.value.$el.focus()
330+
search.value?.focus?.()
319331
}
320332
321333
defineExpose({ setFocus })

frontend/src/components/Calendar/CalendarEventPanel.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343

4444
<!-- Event Details -->
4545
<div v-if="mode == 'details'" class="flex flex-col overflow-y-auto">
46-
<div class="flex items-start gap-2 px-4.5 py-3 pb-0">
46+
<div
47+
class="flex items-start gap-2 px-4.5 py-3 pb-0"
48+
@dblclick="editDetails"
49+
>
4750
<div
4851
class="mx-0.5 my-[5px] size-2.5 rounded-full cursor-pointer"
4952
:style="{
@@ -289,6 +292,9 @@
289292
class="w-[220px]"
290293
v-model="_event.referenceDocname"
291294
:doctype="_event.referenceDoctype"
295+
:filters="
296+
_event.referenceDoctype === 'CRM Lead' ? { converted: 0 } : {}
297+
"
292298
variant="outline"
293299
@update:model-value="sync"
294300
/>
@@ -329,6 +335,9 @@
329335
variant="solid"
330336
class="w-full"
331337
:disabled="!dirty"
338+
:loading="
339+
mode === 'edit' ? events.setValue.loading : events.insert.loading
340+
"
332341
@click="saveEvent"
333342
>
334343
{{
@@ -376,7 +385,7 @@ import {
376385
createDocumentResource,
377386
} from 'frappe-ui'
378387
import ShortcutTooltip from '@/components/ShortcutTooltip.vue'
379-
import { ref, computed, watch, h } from 'vue'
388+
import { ref, computed, watch, h, inject } from 'vue'
380389
import { useRouter } from 'vue-router'
381390
382391
const props = defineProps({
@@ -402,6 +411,8 @@ const { $dialog } = globalStore()
402411
const show = defineModel()
403412
const event = defineModel('event')
404413
414+
const events = inject('events')
415+
405416
const _event = ref({})
406417
407418
const peoples = computed({
@@ -527,6 +538,8 @@ function updateTime(t, fromTime = false) {
527538
}
528539
529540
function saveEvent() {
541+
if (!dirty.value) return
542+
530543
error.value = null
531544
if (!_event.value.title) {
532545
error.value = __('Title is required')

frontend/src/components/Kanban/KanbanView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
<Dropdown :options="actions(column)">
5757
<template #default>
5858
<Button
59-
class="hidden group-hover:flex"
59+
class="opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity"
6060
icon="more-horizontal"
6161
variant="ghost"
6262
/>

0 commit comments

Comments
 (0)