123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
- <script setup lang="ts">
- import { refDebounced } from '@vueuse/shared'
- import { computed } from 'vue'
- import {
- NotificationTypes,
- useNotifications,
- } from '#shared/components/CommonNotifications/index.ts'
- import { useConfirmation } from '#shared/composables/useConfirmation.ts'
- import { useTouchDevice } from '#shared/composables/useTouchDevice.ts'
- import MutationHandler from '#shared/server/apollo/handler/MutationHandler.ts'
- import QueryHandler from '#shared/server/apollo/handler/QueryHandler.ts'
- import SubscriptionHandler from '#shared/server/apollo/handler/SubscriptionHandler.ts'
- import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
- import CommonSectionCollapse from '#desktop/components/CommonSectionCollapse/CommonSectionCollapse.vue'
- import QuickSearchResultList from '#desktop/components/QuickSearch/QuickSearchResultList/QuickSearchResultList.vue'
- import { useRecentSearches } from '#desktop/composables/useRecentSearches.ts'
- import { useUserCurrentRecentViewResetMutation } from '#desktop/entities/user/current/graphql/mutations/userCurrentRecentViewReset.api.ts'
- import { useUserCurrentRecentViewListQuery } from '#desktop/entities/user/current/graphql/queries/userCurrentRecentViewList.api.ts'
- import { useUserCurrentRecentViewUpdatesSubscription } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentRecentViewUpdates.api.ts'
- import { useQuickSearchInput } from './composables/useQuickSearchInput.ts'
- import { lookupQuickSearchPluginComponent } from './plugins/index.ts'
- const DEBOUNCE_TIME = 400
- interface Props {
- collapsed?: boolean
- search: string
- }
- const props = defineProps<Props>()
- const hasSearchInput = computed(() => props.search?.length > 0)
- const debouncedHasSearchInput = refDebounced(hasSearchInput, DEBOUNCE_TIME)
- const { isTouchDevice } = useTouchDevice()
- const { recentSearches, clearSearches, removeSearch } = useRecentSearches()
- const recentViewListQuery = new QueryHandler(
- useUserCurrentRecentViewListQuery({
- limit: 10,
- }),
- )
- const recentViewListQueryResult = recentViewListQuery.result()
- const recentViewListItems = computed(
- () => recentViewListQueryResult.value?.userCurrentRecentViewList ?? [],
- )
- const recentViewUpdatesSubscription = new SubscriptionHandler(
- useUserCurrentRecentViewUpdatesSubscription(),
- )
- recentViewUpdatesSubscription.onResult(({ data }) => {
- if (data?.userCurrentRecentViewUpdates) {
- recentViewListQuery.refetch()
- }
- })
- const { waitForConfirmation } = useConfirmation()
- const { notify } = useNotifications()
- const confirmRemoveRecentSearch = async (searchQuery: string) => {
- const confirmed = await waitForConfirmation(
- __('Are you sure? This recent search will get lost.'),
- { fullscreen: true },
- )
- if (!confirmed) return
- removeSearch(searchQuery)
- notify({
- id: 'recent-search-removed',
- type: NotificationTypes.Success,
- message: __('Recent search was removed successfully.'),
- })
- }
- const confirmClearRecentSearches = async () => {
- const confirmed = await waitForConfirmation(
- __('Are you sure? Your recent searches will get lost.'),
- { fullscreen: true },
- )
- if (!confirmed) return
- clearSearches()
- notify({
- id: 'recent-searches-cleared',
- type: NotificationTypes.Success,
- message: __('Recent searches were cleared successfully.'),
- })
- }
- const recentViewResetMutation = new MutationHandler(
- useUserCurrentRecentViewResetMutation(),
- )
- const confirmClearRecentViewed = async () => {
- const confirmed = await waitForConfirmation(
- __('Are you sure? Your recently viewed items will get lost.'),
- { fullscreen: true },
- )
- if (!confirmed) return
- recentViewResetMutation.send().then(() => {
- notify({
- id: 'recent-viewed-cleared',
- type: NotificationTypes.Success,
- message: __('Recently viewed items were cleared successfully.'),
- })
- })
- }
- const { resetQuickSearchInputField } = useQuickSearchInput()
- </script>
- <template>
- <div class="overflow-x-hidden overflow-y-auto px-3 py-2.5 outline-none">
- <QuickSearchResultList
- v-if="debouncedHasSearchInput && hasSearchInput"
- :search="search"
- :debounce-time="DEBOUNCE_TIME"
- />
- <template
- v-else-if="recentSearches.length > 0 || recentViewListItems.length > 0"
- >
- <CommonSectionCollapse
- v-if="recentSearches.length > 0"
- id="page-recent-searches"
- :title="__('Recent searches')"
- :no-header="collapsed"
- no-collapse
- >
- <template #default="{ headerId }">
- <nav :aria-labelledby="headerId">
- <ul class="m-0 flex flex-col gap-1 p-0">
- <li
- v-for="searchQuery in recentSearches"
- :key="searchQuery"
- class="group/recent-search flex justify-center"
- >
- <CommonLink
- class="relative flex grow items-center gap-2 rounded-md px-2 py-3 text-neutral-400 hover:bg-blue-900 hover:no-underline!"
- :link="`/search/${searchQuery}`"
- exact-active-class="bg-blue-800! w-full text-white!"
- internal
- @click="resetQuickSearchInputField"
- >
- <CommonIcon name="search" size="tiny" />
- <CommonLabel class="gap-2 text-white!">
- {{ searchQuery }}
- </CommonLabel>
- <CommonButton
- :aria-label="$t('Delete this recent search')"
- :class="{
- 'opacity-0 transition-opacity': !isTouchDevice,
- }"
- class="absolute end-2 top-3 justify-end group-hover/recent-search:opacity-100 focus:opacity-100"
- icon="x-lg"
- size="small"
- variant="remove"
- @click.stop.prevent="confirmRemoveRecentSearch(searchQuery)"
- />
- </CommonLink>
- </li>
- </ul>
- <div class="mt-2 mb-1 flex justify-end">
- <CommonLink
- link="#"
- size="small"
- @click="confirmClearRecentSearches"
- >
- {{ $t('Clear recent searches') }}
- </CommonLink>
- </div>
- </nav>
- </template>
- </CommonSectionCollapse>
- <CommonSectionCollapse
- v-if="recentViewListItems.length > 0"
- id="page-recently-viewed"
- :title="__('Recently viewed')"
- :no-header="collapsed"
- no-collapse
- >
- <template #default="{ headerId }">
- <nav :aria-labelledby="headerId">
- <ul class="m-0 flex flex-col gap-1 p-0">
- <li
- v-for="item in recentViewListItems"
- :key="item.id"
- class="relative"
- >
- <component
- :is="lookupQuickSearchPluginComponent(item.__typename!)"
- :item="item"
- mode="recently-viewed"
- @click="resetQuickSearchInputField"
- />
- </li>
- </ul>
- <div class="mt-2 mb-1 flex justify-end">
- <CommonLink
- link="#"
- size="small"
- @click="confirmClearRecentViewed"
- >
- {{ $t('Clear recently viewed') }}
- </CommonLink>
- </div>
- </nav>
- </template>
- </CommonSectionCollapse>
- </template>
- <CommonLabel v-else>
- {{
- $t('Start typing i.e. the name of a ticket, an organization or a user.')
- }}
- </CommonLabel>
- </div>
- </template>
|