fix: apply ESLint rule vue/component-tags-order
This commit is contained in:
@@ -1,30 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
|
||||
id="app"
|
||||
class="flex-grow-0 w-full h-full min-h-0 app-wrapper"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||
:dir="isRTLView ? 'rtl' : 'ltr'"
|
||||
>
|
||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||
<template v-if="currentAccountId">
|
||||
<pending-email-verification-banner v-if="hideOnOnboardingView" />
|
||||
<payment-pending-banner v-if="hideOnOnboardingView" />
|
||||
<upgrade-banner />
|
||||
</template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
</transition>
|
||||
<add-account-modal
|
||||
:show="showAddAccountModal"
|
||||
:has-accounts="hasAccounts"
|
||||
/>
|
||||
<woot-snackbar-box />
|
||||
<network-notification />
|
||||
</div>
|
||||
<loading-state v-else />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import router from '../dashboard/routes';
|
||||
@@ -147,6 +120,33 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
|
||||
id="app"
|
||||
class="flex-grow-0 w-full h-full min-h-0 app-wrapper"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||
:dir="isRTLView ? 'rtl' : 'ltr'"
|
||||
>
|
||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||
<template v-if="currentAccountId">
|
||||
<pending-email-verification-banner v-if="hideOnOnboardingView" />
|
||||
<payment-pending-banner v-if="hideOnOnboardingView" />
|
||||
<upgrade-banner />
|
||||
</template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
</transition>
|
||||
<add-account-modal
|
||||
:show="showAddAccountModal"
|
||||
:has-accounts="hasAccounts"
|
||||
/>
|
||||
<woot-snackbar-box />
|
||||
<network-notification />
|
||||
</div>
|
||||
<loading-state v-else />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './assets/scss/app';
|
||||
</style>
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
<template>
|
||||
<div class="-mt-px text-sm">
|
||||
<button
|
||||
class="flex items-center select-none w-full rounded-none bg-slate-50 dark:bg-slate-800 border border-l-0 border-r-0 border-solid m-0 border-slate-100 dark:border-slate-700/50 cursor-grab justify-between py-2 px-4 drag-handle"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<emoji-or-icon class="inline-block w-5" :icon="icon" :emoji="emoji" />
|
||||
<h5
|
||||
class="text-slate-800 text-sm dark:text-slate-100 mb-0 py-0 pr-2 pl-0"
|
||||
>
|
||||
{{ title }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<slot name="button" />
|
||||
<div class="flex justify-end w-3 text-woot-500">
|
||||
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
|
||||
<fluent-icon v-else size="24" icon="add" type="solid" />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="bg-white dark:bg-slate-900"
|
||||
:class="compact ? 'p-0' : 'p-4'"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||
|
||||
@@ -61,3 +29,35 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="-mt-px text-sm">
|
||||
<button
|
||||
class="flex items-center select-none w-full rounded-none bg-slate-50 dark:bg-slate-800 border border-l-0 border-r-0 border-solid m-0 border-slate-100 dark:border-slate-700/50 cursor-grab justify-between py-2 px-4 drag-handle"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<emoji-or-icon class="inline-block w-5" :icon="icon" :emoji="emoji" />
|
||||
<h5
|
||||
class="text-slate-800 text-sm dark:text-slate-100 mb-0 py-0 pr-2 pl-0"
|
||||
>
|
||||
{{ title }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<slot name="button" />
|
||||
<div class="flex justify-end w-3 text-woot-500">
|
||||
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
|
||||
<fluent-icon v-else size="24" icon="add" type="solid" />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="bg-white dark:bg-slate-900"
|
||||
:class="compact ? 'p-0' : 'p-4'"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
class="bg-white dark:bg-slate-900 cursor-pointer flex flex-col justify-end transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800 hover:border-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<img :src="src" :alt="title" class="w-1/2 my-4 mx-auto" />
|
||||
<h3
|
||||
class="text-slate-800 dark:text-slate-100 text-base text-center capitalize"
|
||||
>
|
||||
{{ title }}
|
||||
</h3>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -27,6 +13,20 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="bg-white dark:bg-slate-900 cursor-pointer flex flex-col justify-end transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800 hover:border-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<img :src="src" :alt="title" class="w-1/2 my-4 mx-auto" />
|
||||
<h3
|
||||
class="text-slate-800 dark:text-slate-100 text-base text-center capitalize"
|
||||
>
|
||||
{{ title }}
|
||||
</h3>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.inactive {
|
||||
img {
|
||||
|
||||
@@ -1,119 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-shrink-0 overflow-hidden border-r conversations-list-wrap rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
|
||||
:class="[
|
||||
{ hidden: !showConversationList },
|
||||
isOnExpandedLayout ? 'basis-full' : 'flex-basis-clamp',
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
<chat-list-header
|
||||
:page-title="pageTitle"
|
||||
:has-applied-filters="hasAppliedFilters"
|
||||
:has-active-folders="hasActiveFolders"
|
||||
:active-status="activeStatus"
|
||||
@add-folders="onClickOpenAddFoldersModal"
|
||||
@delete-folders="onClickOpenDeleteFoldersModal"
|
||||
@filters-modal="onToggleAdvanceFiltersModal"
|
||||
@reset-filters="resetAndFetchData"
|
||||
@basic-filter-change="onBasicFilterChange"
|
||||
/>
|
||||
|
||||
<add-custom-views
|
||||
v-if="showAddFoldersModal"
|
||||
:custom-views-query="foldersQuery"
|
||||
:open-last-saved-item="openLastSavedItemInFolder"
|
||||
@close="onCloseAddFoldersModal"
|
||||
/>
|
||||
|
||||
<delete-custom-views
|
||||
v-if="showDeleteFoldersModal"
|
||||
:show-delete-popup.sync="showDeleteFoldersModal"
|
||||
:active-custom-view="activeFolder"
|
||||
:custom-views-id="foldersId"
|
||||
:open-last-item-after-delete="openLastItemAfterDeleteInFolder"
|
||||
@close="onCloseDeleteFoldersModal"
|
||||
/>
|
||||
|
||||
<chat-type-tabs
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
:items="assigneeTabItems"
|
||||
:active-tab="activeAssigneeTab"
|
||||
class="tab--chat-type"
|
||||
@chatTabChange="updateAssigneeTab"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="!chatListLoading && !conversationList.length"
|
||||
class="flex items-center justify-center p-4 overflow-auto"
|
||||
>
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
<conversation-bulk-actions
|
||||
v-if="selectedConversations.length"
|
||||
:conversations="selectedConversations"
|
||||
:all-conversations-selected="allConversationsSelected"
|
||||
:selected-inboxes="uniqueInboxes"
|
||||
:show-open-action="allSelectedConversationsStatus('open')"
|
||||
:show-resolved-action="allSelectedConversationsStatus('resolved')"
|
||||
:show-snoozed-action="allSelectedConversationsStatus('snoozed')"
|
||||
@select-all-conversations="selectAllConversations"
|
||||
@assign-agent="onAssignAgent"
|
||||
@update-conversations="onUpdateConversations"
|
||||
@assign-labels="onAssignLabels"
|
||||
@assign-team="onAssignTeamsForBulk"
|
||||
/>
|
||||
<div
|
||||
ref="conversationList"
|
||||
class="flex-1 conversations-list"
|
||||
:class="{ 'overflow-hidden': isContextMenuOpen }"
|
||||
>
|
||||
<virtual-list
|
||||
ref="conversationVirtualList"
|
||||
:data-key="'id'"
|
||||
:data-sources="conversationList"
|
||||
:data-component="itemComponent"
|
||||
:extra-props="virtualListExtraProps"
|
||||
class="w-full h-full overflow-auto"
|
||||
footer-tag="div"
|
||||
>
|
||||
<template #footer>
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="mt-4 mb-4 spinner" />
|
||||
</div>
|
||||
<p
|
||||
v-if="showEndOfListMessage"
|
||||
class="p-4 text-center text-slate-400 dark:text-slate-300"
|
||||
>
|
||||
{{ $t('CHAT_LIST.EOF') }}
|
||||
</p>
|
||||
<intersection-observer
|
||||
v-if="!showEndOfListMessage && !chatListLoading"
|
||||
:options="infiniteLoaderOptions"
|
||||
@observed="loadMoreConversations"
|
||||
/>
|
||||
</template>
|
||||
</virtual-list>
|
||||
</div>
|
||||
<woot-modal
|
||||
:show.sync="showAdvancedFilters"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
size="medium"
|
||||
>
|
||||
<conversation-advanced-filter
|
||||
v-if="showAdvancedFilters"
|
||||
:initial-filter-types="advancedFilterTypes"
|
||||
:initial-applied-filters="appliedFilter"
|
||||
:active-folder-name="activeFolderName"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
:is-folder-view="hasActiveFolders"
|
||||
@applyFilter="onApplyFilter"
|
||||
@updateFolder="onUpdateSavedFilter"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
@@ -980,6 +864,122 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-shrink-0 overflow-hidden border-r conversations-list-wrap rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
|
||||
:class="[
|
||||
{ hidden: !showConversationList },
|
||||
isOnExpandedLayout ? 'basis-full' : 'flex-basis-clamp',
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
<chat-list-header
|
||||
:page-title="pageTitle"
|
||||
:has-applied-filters="hasAppliedFilters"
|
||||
:has-active-folders="hasActiveFolders"
|
||||
:active-status="activeStatus"
|
||||
@add-folders="onClickOpenAddFoldersModal"
|
||||
@delete-folders="onClickOpenDeleteFoldersModal"
|
||||
@filters-modal="onToggleAdvanceFiltersModal"
|
||||
@reset-filters="resetAndFetchData"
|
||||
@basic-filter-change="onBasicFilterChange"
|
||||
/>
|
||||
|
||||
<add-custom-views
|
||||
v-if="showAddFoldersModal"
|
||||
:custom-views-query="foldersQuery"
|
||||
:open-last-saved-item="openLastSavedItemInFolder"
|
||||
@close="onCloseAddFoldersModal"
|
||||
/>
|
||||
|
||||
<delete-custom-views
|
||||
v-if="showDeleteFoldersModal"
|
||||
:show-delete-popup.sync="showDeleteFoldersModal"
|
||||
:active-custom-view="activeFolder"
|
||||
:custom-views-id="foldersId"
|
||||
:open-last-item-after-delete="openLastItemAfterDeleteInFolder"
|
||||
@close="onCloseDeleteFoldersModal"
|
||||
/>
|
||||
|
||||
<chat-type-tabs
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
:items="assigneeTabItems"
|
||||
:active-tab="activeAssigneeTab"
|
||||
class="tab--chat-type"
|
||||
@chatTabChange="updateAssigneeTab"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="!chatListLoading && !conversationList.length"
|
||||
class="flex items-center justify-center p-4 overflow-auto"
|
||||
>
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
<conversation-bulk-actions
|
||||
v-if="selectedConversations.length"
|
||||
:conversations="selectedConversations"
|
||||
:all-conversations-selected="allConversationsSelected"
|
||||
:selected-inboxes="uniqueInboxes"
|
||||
:show-open-action="allSelectedConversationsStatus('open')"
|
||||
:show-resolved-action="allSelectedConversationsStatus('resolved')"
|
||||
:show-snoozed-action="allSelectedConversationsStatus('snoozed')"
|
||||
@select-all-conversations="selectAllConversations"
|
||||
@assign-agent="onAssignAgent"
|
||||
@update-conversations="onUpdateConversations"
|
||||
@assign-labels="onAssignLabels"
|
||||
@assign-team="onAssignTeamsForBulk"
|
||||
/>
|
||||
<div
|
||||
ref="conversationList"
|
||||
class="flex-1 conversations-list"
|
||||
:class="{ 'overflow-hidden': isContextMenuOpen }"
|
||||
>
|
||||
<virtual-list
|
||||
ref="conversationVirtualList"
|
||||
:data-key="'id'"
|
||||
:data-sources="conversationList"
|
||||
:data-component="itemComponent"
|
||||
:extra-props="virtualListExtraProps"
|
||||
class="w-full h-full overflow-auto"
|
||||
footer-tag="div"
|
||||
>
|
||||
<template #footer>
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="mt-4 mb-4 spinner" />
|
||||
</div>
|
||||
<p
|
||||
v-if="showEndOfListMessage"
|
||||
class="p-4 text-center text-slate-400 dark:text-slate-300"
|
||||
>
|
||||
{{ $t('CHAT_LIST.EOF') }}
|
||||
</p>
|
||||
<intersection-observer
|
||||
v-if="!showEndOfListMessage && !chatListLoading"
|
||||
:options="infiniteLoaderOptions"
|
||||
@observed="loadMoreConversations"
|
||||
/>
|
||||
</template>
|
||||
</virtual-list>
|
||||
</div>
|
||||
<woot-modal
|
||||
:show.sync="showAdvancedFilters"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
size="medium"
|
||||
>
|
||||
<conversation-advanced-filter
|
||||
v-if="showAdvancedFilters"
|
||||
:initial-filter-types="advancedFilterTypes"
|
||||
:initial-applied-filters="appliedFilter"
|
||||
:active-folder-name="activeFolderName"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
:is-folder-view="hasActiveFolders"
|
||||
@applyFilter="onApplyFilter"
|
||||
@updateFolder="onUpdateSavedFilter"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
<template>
|
||||
<div class="code--container">
|
||||
<div class="code--action-area">
|
||||
<form
|
||||
v-if="enableCodePen"
|
||||
class="code--codeopen-form"
|
||||
action="https://codepen.io/pen/define"
|
||||
method="POST"
|
||||
target="_blank"
|
||||
>
|
||||
<input type="hidden" name="data" :value="codepenScriptValue" />
|
||||
|
||||
<button type="submit" class="button secondary tiny">
|
||||
{{ $t('COMPONENTS.CODE.CODEPEN') }}
|
||||
</button>
|
||||
</form>
|
||||
<button class="button secondary tiny" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</button>
|
||||
</div>
|
||||
<highlightjs v-if="script" :language="lang" :code="script" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
@@ -66,6 +42,30 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code--container">
|
||||
<div class="code--action-area">
|
||||
<form
|
||||
v-if="enableCodePen"
|
||||
class="code--codeopen-form"
|
||||
action="https://codepen.io/pen/define"
|
||||
method="POST"
|
||||
target="_blank"
|
||||
>
|
||||
<input type="hidden" name="data" :value="codepenScriptValue" />
|
||||
|
||||
<button type="submit" class="button secondary tiny">
|
||||
{{ $t('COMPONENTS.CODE.CODEPEN') }}
|
||||
</button>
|
||||
</form>
|
||||
<button class="button secondary tiny" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</button>
|
||||
</div>
|
||||
<highlightjs v-if="script" :language="lang" :code="script" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code--container {
|
||||
position: relative;
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
<template>
|
||||
<conversation-card
|
||||
:key="source.id"
|
||||
:active-label="label"
|
||||
:team-id="teamId"
|
||||
:folders-id="foldersId"
|
||||
:chat="source"
|
||||
:conversation-type="conversationType"
|
||||
:selected="isConversationSelected(source.id)"
|
||||
:show-assignee="showAssignee"
|
||||
:enable-context-menu="true"
|
||||
@select-conversation="selectConversation"
|
||||
@de-select-conversation="deSelectConversation"
|
||||
@assign-agent="assignAgent"
|
||||
@assign-team="assignTeam"
|
||||
@assign-label="assignLabels"
|
||||
@update-conversation-status="updateConversationStatus"
|
||||
@context-menu-toggle="toggleContextMenu"
|
||||
@mark-as-unread="markAsUnread"
|
||||
@assign-priority="assignPriority"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConversationCard from './widgets/conversation/ConversationCard.vue';
|
||||
export default {
|
||||
@@ -70,3 +47,26 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<conversation-card
|
||||
:key="source.id"
|
||||
:active-label="label"
|
||||
:team-id="teamId"
|
||||
:folders-id="foldersId"
|
||||
:chat="source"
|
||||
:conversation-type="conversationType"
|
||||
:selected="isConversationSelected(source.id)"
|
||||
:show-assignee="showAssignee"
|
||||
:enable-context-menu="true"
|
||||
@select-conversation="selectConversation"
|
||||
@de-select-conversation="deSelectConversation"
|
||||
@assign-agent="assignAgent"
|
||||
@assign-team="assignTeam"
|
||||
@assign-label="assignLabels"
|
||||
@update-conversation-status="updateConversationStatus"
|
||||
@context-menu-toggle="toggleContextMenu"
|
||||
@mark-as-unread="markAsUnread"
|
||||
@assign-priority="assignPriority"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,139 +1,3 @@
|
||||
<template>
|
||||
<div class="py-3 px-4">
|
||||
<div class="flex items-center mb-1">
|
||||
<h4 class="text-sm flex items-center m-0 w-full error">
|
||||
<div v-if="isAttributeTypeCheckbox" class="flex items-center">
|
||||
<input
|
||||
v-model="editedValue"
|
||||
class="!my-0 mr-2 ml-0"
|
||||
type="checkbox"
|
||||
@change="onUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<span
|
||||
class="w-full inline-flex gap-1.5 items-start font-medium whitespace-nowrap text-sm mb-0"
|
||||
:class="
|
||||
v$.editedValue.$error
|
||||
? 'text-red-400 dark:text-red-500'
|
||||
: 'text-slate-800 dark:text-slate-100'
|
||||
"
|
||||
>
|
||||
{{ label }}
|
||||
<helper-text-popup
|
||||
v-if="description"
|
||||
:message="description"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
</span>
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-tooltip.left="$t('CUSTOM_ATTRIBUTES.ACTIONS.DELETE')"
|
||||
variant="link"
|
||||
size="medium"
|
||||
color-scheme="secondary"
|
||||
icon="delete"
|
||||
class-names="flex justify-end w-4"
|
||||
@click="onDelete"
|
||||
/>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="notAttributeTypeCheckboxAndList">
|
||||
<div v-if="isEditing" v-on-clickaway="onClickAway">
|
||||
<div class="mb-2 w-full flex items-center">
|
||||
<input
|
||||
ref="inputfield"
|
||||
v-model="editedValue"
|
||||
:type="inputType"
|
||||
class="!h-8 ltr:!rounded-r-none rtl:!rounded-l-none !mb-0 !text-sm"
|
||||
autofocus="true"
|
||||
:class="{ error: v$.editedValue.$error }"
|
||||
@blur="v$.editedValue.$touch"
|
||||
@keyup.enter="onUpdate"
|
||||
/>
|
||||
<div>
|
||||
<woot-button
|
||||
size="small"
|
||||
icon="checkmark"
|
||||
class="rounded-l-none rtl:rounded-r-none"
|
||||
@click="onUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
v-if="shouldShowErrorMessage"
|
||||
class="text-red-400 dark:text-red-500 text-sm block font-normal -mt-px w-full"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-show="!isEditing"
|
||||
class="flex group"
|
||||
:class="{ 'is-editable': showActions }"
|
||||
>
|
||||
<a
|
||||
v-if="isAttributeTypeLink"
|
||||
:href="hrefURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ urlValue }}
|
||||
</a>
|
||||
<p
|
||||
v-else
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ displayValue || '---' }}
|
||||
</p>
|
||||
<div class="flex max-w-[2rem] gap-1 ml-1 rtl:mr-1 rtl:ml-0">
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-tooltip="$t('CUSTOM_ATTRIBUTES.ACTIONS.COPY')"
|
||||
variant="link"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="clipboard"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
@click="onCopy"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showActions"
|
||||
v-tooltip.right="$t('CUSTOM_ATTRIBUTES.ACTIONS.EDIT')"
|
||||
variant="link"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
@click="onEdit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isAttributeTypeList">
|
||||
<multiselect-dropdown
|
||||
:options="listOptions"
|
||||
:selected-item="selectedItem"
|
||||
:has-thumbnail="false"
|
||||
:multiselector-placeholder="
|
||||
$t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.PLACEHOLDER')
|
||||
"
|
||||
:no-search-result="
|
||||
$t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.NO_RESULT')
|
||||
"
|
||||
:input-placeholder="
|
||||
$t(
|
||||
'CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.SEARCH_INPUT_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
@click="onUpdateListValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { required, url } from '@vuelidate/validators';
|
||||
@@ -330,6 +194,142 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="py-3 px-4">
|
||||
<div class="flex items-center mb-1">
|
||||
<h4 class="text-sm flex items-center m-0 w-full error">
|
||||
<div v-if="isAttributeTypeCheckbox" class="flex items-center">
|
||||
<input
|
||||
v-model="editedValue"
|
||||
class="!my-0 mr-2 ml-0"
|
||||
type="checkbox"
|
||||
@change="onUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<span
|
||||
class="w-full inline-flex gap-1.5 items-start font-medium whitespace-nowrap text-sm mb-0"
|
||||
:class="
|
||||
v$.editedValue.$error
|
||||
? 'text-red-400 dark:text-red-500'
|
||||
: 'text-slate-800 dark:text-slate-100'
|
||||
"
|
||||
>
|
||||
{{ label }}
|
||||
<helper-text-popup
|
||||
v-if="description"
|
||||
:message="description"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
</span>
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-tooltip.left="$t('CUSTOM_ATTRIBUTES.ACTIONS.DELETE')"
|
||||
variant="link"
|
||||
size="medium"
|
||||
color-scheme="secondary"
|
||||
icon="delete"
|
||||
class-names="flex justify-end w-4"
|
||||
@click="onDelete"
|
||||
/>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="notAttributeTypeCheckboxAndList">
|
||||
<div v-if="isEditing" v-on-clickaway="onClickAway">
|
||||
<div class="mb-2 w-full flex items-center">
|
||||
<input
|
||||
ref="inputfield"
|
||||
v-model="editedValue"
|
||||
:type="inputType"
|
||||
class="!h-8 ltr:!rounded-r-none rtl:!rounded-l-none !mb-0 !text-sm"
|
||||
autofocus="true"
|
||||
:class="{ error: v$.editedValue.$error }"
|
||||
@blur="v$.editedValue.$touch"
|
||||
@keyup.enter="onUpdate"
|
||||
/>
|
||||
<div>
|
||||
<woot-button
|
||||
size="small"
|
||||
icon="checkmark"
|
||||
class="rounded-l-none rtl:rounded-r-none"
|
||||
@click="onUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
v-if="shouldShowErrorMessage"
|
||||
class="text-red-400 dark:text-red-500 text-sm block font-normal -mt-px w-full"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-show="!isEditing"
|
||||
class="flex group"
|
||||
:class="{ 'is-editable': showActions }"
|
||||
>
|
||||
<a
|
||||
v-if="isAttributeTypeLink"
|
||||
:href="hrefURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ urlValue }}
|
||||
</a>
|
||||
<p
|
||||
v-else
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ displayValue || '---' }}
|
||||
</p>
|
||||
<div class="flex max-w-[2rem] gap-1 ml-1 rtl:mr-1 rtl:ml-0">
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-tooltip="$t('CUSTOM_ATTRIBUTES.ACTIONS.COPY')"
|
||||
variant="link"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="clipboard"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
@click="onCopy"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showActions"
|
||||
v-tooltip.right="$t('CUSTOM_ATTRIBUTES.ACTIONS.EDIT')"
|
||||
variant="link"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
@click="onEdit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isAttributeTypeList">
|
||||
<multiselect-dropdown
|
||||
:options="listOptions"
|
||||
:selected-item="selectedItem"
|
||||
:has-thumbnail="false"
|
||||
:multiselector-placeholder="
|
||||
$t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.PLACEHOLDER')
|
||||
"
|
||||
:no-search-result="
|
||||
$t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.NO_RESULT')
|
||||
"
|
||||
:input-placeholder="
|
||||
$t(
|
||||
'CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_TYPE.LIST.SEARCH_INPUT_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
@click="onUpdateListValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.selector-wrap {
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<woot-modal-header :header-title="$t('CONVERSATION.CUSTOM_SNOOZE.TITLE')" />
|
||||
<form class="modal-content" @submit.prevent="chooseTime">
|
||||
<date-picker
|
||||
v-model="snoozeTime"
|
||||
type="datetime"
|
||||
inline
|
||||
:lang="lang"
|
||||
:disabled-date="disabledDate"
|
||||
:disabled-time="disabledTime"
|
||||
:popup-style="{ width: '100%' }"
|
||||
/>
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
|
||||
</woot-button>
|
||||
<woot-button>
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
|
||||
@@ -64,6 +39,31 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<woot-modal-header :header-title="$t('CONVERSATION.CUSTOM_SNOOZE.TITLE')" />
|
||||
<form class="modal-content" @submit.prevent="chooseTime">
|
||||
<date-picker
|
||||
v-model="snoozeTime"
|
||||
type="datetime"
|
||||
inline
|
||||
:lang="lang"
|
||||
:disabled-date="disabledDate"
|
||||
:disabled-time="disabledTime"
|
||||
:popup-style="{ width: '100%' }"
|
||||
/>
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
|
||||
</woot-button>
|
||||
<woot-button>
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.modal-content {
|
||||
@apply pt-2 px-5 pb-6;
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start w-full gap-6">
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
@@ -16,16 +29,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<template>
|
||||
<div ref="observedElement" class="h-6 w-full" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -32,3 +28,7 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="observedElement" class="h-6 w-full" />
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<div class="text--container">
|
||||
<woot-button size="small" class="button--text" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
size="small"
|
||||
class="button--visibility"
|
||||
color-scheme="secondary"
|
||||
:icon="masked ? 'eye-show' : 'eye-hide'"
|
||||
@click.prevent="toggleMasked"
|
||||
/>
|
||||
<highlightjs v-if="value" :code="masked ? '•'.repeat(10) : value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
@@ -45,6 +28,23 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text--container">
|
||||
<woot-button size="small" class="button--text" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
size="small"
|
||||
class="button--visibility"
|
||||
color-scheme="secondary"
|
||||
:icon="masked ? 'eye-show' : 'eye-hide'"
|
||||
@click.prevent="toggleMasked"
|
||||
/>
|
||||
<highlightjs v-if="value" :code="masked ? '•'.repeat(10) : value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text--container {
|
||||
position: relative;
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-if="show"
|
||||
:class="modalClassName"
|
||||
transition="modal"
|
||||
@mousedown="handleMouseDown"
|
||||
>
|
||||
<div
|
||||
:class="{
|
||||
'modal-container rtl:text-right shadow-md max-h-full overflow-auto relative bg-white dark:bg-slate-800 skip-context-menu': true,
|
||||
'rounded-xl w-[37.5rem]': !fullWidth,
|
||||
'items-center rounded-none flex h-full justify-center w-full':
|
||||
fullWidth,
|
||||
[size]: true,
|
||||
}"
|
||||
@mouse.stop
|
||||
@mousedown="event => event.stopPropagation()"
|
||||
>
|
||||
<woot-button
|
||||
v-if="showCloseButton"
|
||||
color-scheme="secondary"
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
class="absolute z-10 ltr:right-2 rtl:left-2 top-2"
|
||||
@click="close"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -108,6 +75,39 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-if="show"
|
||||
:class="modalClassName"
|
||||
transition="modal"
|
||||
@mousedown="handleMouseDown"
|
||||
>
|
||||
<div
|
||||
:class="{
|
||||
'modal-container rtl:text-right shadow-md max-h-full overflow-auto relative bg-white dark:bg-slate-800 skip-context-menu': true,
|
||||
'rounded-xl w-[37.5rem]': !fullWidth,
|
||||
'items-center rounded-none flex h-full justify-center w-full':
|
||||
fullWidth,
|
||||
[size]: true,
|
||||
}"
|
||||
@mouse.stop
|
||||
@mousedown="event => event.stopPropagation()"
|
||||
>
|
||||
<woot-button
|
||||
v-if="showCloseButton"
|
||||
color-scheme="secondary"
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
class="absolute z-10 ltr:right-2 rtl:left-2 top-2"
|
||||
@click="close"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-mask {
|
||||
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContentValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start px-8 pt-8 pb-0">
|
||||
<img v-if="headerImage" :src="headerImage" alt="No image" />
|
||||
@@ -23,26 +46,3 @@
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContentValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="ml-0 mr-0 flex py-8 w-full xl:w-3/4 flex-col xl:flex-row"
|
||||
@@ -30,26 +53,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<woot-button
|
||||
:size="size"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
class="-ml-3 text-black-900 dark:text-slate-300"
|
||||
icon="list"
|
||||
@click="onMenuItemClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
@@ -26,3 +15,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-button
|
||||
:size="size"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
class="-ml-3 text-black-900 dark:text-slate-300"
|
||||
icon="list"
|
||||
@click="onMenuItemClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="shadow-sm bg-slate-800 dark:bg-slate-700 rounded-[4px] items-center gap-3 inline-flex mb-2 max-w-[25rem] min-h-[1.875rem] min-w-[15rem] px-6 py-3 text-left"
|
||||
>
|
||||
<div class="text-white dark:text-white text-sm font-medium">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div v-if="action">
|
||||
<router-link
|
||||
v-if="action.type == 'link'"
|
||||
:to="action.to"
|
||||
class="text-woot-500 dark:text-woot-500 cursor-pointer font-medium hover:text-woot-600 dark:hover:text-woot-600 select-none"
|
||||
>
|
||||
{{ action.message }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -42,3 +21,24 @@ export default {
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="shadow-sm bg-slate-800 dark:bg-slate-700 rounded-[4px] items-center gap-3 inline-flex mb-2 max-w-[25rem] min-h-[1.875rem] min-w-[15rem] px-6 py-3 text-left"
|
||||
>
|
||||
<div class="text-white dark:text-white text-sm font-medium">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div v-if="action">
|
||||
<router-link
|
||||
v-if="action.type == 'link'"
|
||||
:to="action.to"
|
||||
class="text-woot-500 dark:text-woot-500 cursor-pointer font-medium hover:text-woot-600 dark:hover:text-woot-600 select-none"
|
||||
>
|
||||
{{ action.message }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
<template>
|
||||
<transition-group
|
||||
name="toast-fade"
|
||||
tag="div"
|
||||
class="left-0 my-0 mx-auto max-w-[25rem] overflow-hidden absolute right-0 text-center top-4 z-[9999]"
|
||||
>
|
||||
<woot-snackbar
|
||||
v-for="snackMessage in snackMessages"
|
||||
:key="snackMessage.key"
|
||||
:message="snackMessage.message"
|
||||
:action="snackMessage.action"
|
||||
/>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootSnackbar from './Snackbar.vue';
|
||||
|
||||
@@ -53,3 +38,18 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition-group
|
||||
name="toast-fade"
|
||||
tag="div"
|
||||
class="left-0 my-0 mx-auto max-w-[25rem] overflow-hidden absolute right-0 text-center top-4 z-[9999]"
|
||||
>
|
||||
<woot-snackbar
|
||||
v-for="snackMessage in snackMessages"
|
||||
:key="snackMessage.key"
|
||||
:message="snackMessage.message"
|
||||
:action="snackMessage.action"
|
||||
/>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
has-action-button
|
||||
@click="routeToBilling"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
@@ -86,3 +75,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
has-action-button
|
||||
@click="routeToBilling"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
action-button-icon="mail"
|
||||
has-action-button
|
||||
@click="resendVerificationEmail"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -41,3 +29,15 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
action-button-icon="mail"
|
||||
has-action-button
|
||||
@click="resendVerificationEmail"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="primary"
|
||||
:banner-message="bannerMessage"
|
||||
href-link="https://github.com/chatwoot/chatwoot/releases"
|
||||
:href-link-text="$t('GENERAL_SETTINGS.LEARN_MORE')"
|
||||
has-close-button
|
||||
@close="dismissUpdateBanner"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
@@ -77,3 +66,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="primary"
|
||||
:banner-message="bannerMessage"
|
||||
href-link="https://github.com/chatwoot/chatwoot/releases"
|
||||
:href-link-text="$t('GENERAL_SETTINGS.LEARN_MORE')"
|
||||
has-close-button
|
||||
@close="dismissUpdateBanner"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
has-action-button
|
||||
@click="routeToBilling"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -86,3 +75,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
has-action-button
|
||||
@click="routeToBilling"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
<template>
|
||||
<kbd class="hotkey p-0.5 min-w-[1rem] uppercase" :class="customClass">
|
||||
<slot />
|
||||
</kbd>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -13,6 +8,11 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<kbd class="hotkey p-0.5 min-w-[1rem] uppercase" :class="customClass">
|
||||
<slot />
|
||||
</kbd>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
kbd.hotkey {
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<template>
|
||||
<button :type="type" class="button nice" :class="variant" @click="onClick">
|
||||
<fluent-icon
|
||||
v-if="!isLoading && icon"
|
||||
class="icon"
|
||||
:class="buttonIconClass"
|
||||
:icon="icon"
|
||||
/>
|
||||
<spinner v-if="isLoading" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -42,3 +29,16 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" class="button nice" :class="variant" @click="onClick">
|
||||
<fluent-icon
|
||||
v-if="!isLoading && icon"
|
||||
class="icon"
|
||||
:class="buttonIconClass"
|
||||
:icon="icon"
|
||||
/>
|
||||
<spinner v-if="isLoading" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
:type="type"
|
||||
data-testid="submit_button"
|
||||
:disabled="disabled"
|
||||
:class="computedClass"
|
||||
@click="onClick"
|
||||
>
|
||||
<fluent-icon v-if="!!iconClass" :icon="iconClass" class="icon" />
|
||||
<span>{{ buttonText }}</span>
|
||||
<spinner v-if="loading" class="ml-2" :color-scheme="spinnerClass" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
|
||||
@@ -61,6 +47,20 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:type="type"
|
||||
data-testid="submit_button"
|
||||
:disabled="disabled"
|
||||
:class="computedClass"
|
||||
@click="onClick"
|
||||
>
|
||||
<fluent-icon v-if="!!iconClass" :icon="iconClass" class="icon" />
|
||||
<span>{{ buttonText }}</span>
|
||||
<spinner v-if="loading" class="ml-2" :color-scheme="spinnerClass" />
|
||||
</button>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
button:disabled {
|
||||
@apply bg-woot-100 dark:bg-woot-500/25 dark:text-slate-500 opacity-100;
|
||||
|
||||
@@ -1,81 +1,3 @@
|
||||
<template>
|
||||
<div class="relative flex items-center justify-end resolve-actions">
|
||||
<div class="button-group">
|
||||
<woot-button
|
||||
v-if="isOpen"
|
||||
class-names="resolve"
|
||||
color-scheme="success"
|
||||
icon="checkmark"
|
||||
emoji="✅"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdResolveConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.RESOLVE_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="isResolved"
|
||||
class-names="resolve"
|
||||
color-scheme="warning"
|
||||
icon="arrow-redo"
|
||||
emoji="👀"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.REOPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="showOpenButton"
|
||||
class-names="resolve"
|
||||
color-scheme="primary"
|
||||
icon="person"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.OPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showAdditionalActions"
|
||||
ref="arrowDownButton"
|
||||
:color-scheme="buttonClass"
|
||||
:disabled="isLoading"
|
||||
icon="chevron-down"
|
||||
emoji="🔽"
|
||||
@click="openDropdown"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showActionsDropdown"
|
||||
v-on-clickaway="closeDropdown"
|
||||
class="dropdown-pane dropdown-pane--open"
|
||||
>
|
||||
<woot-dropdown-menu class="mb-0">
|
||||
<woot-dropdown-item v-if="!isPending">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
size="small"
|
||||
icon="snooze"
|
||||
@click="() => openSnoozeModal()"
|
||||
>
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-item v-if="!isPending">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
size="small"
|
||||
icon="book-clock"
|
||||
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
|
||||
>
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
</woot-dropdown-menu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
@@ -220,6 +142,84 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative flex items-center justify-end resolve-actions">
|
||||
<div class="button-group">
|
||||
<woot-button
|
||||
v-if="isOpen"
|
||||
class-names="resolve"
|
||||
color-scheme="success"
|
||||
icon="checkmark"
|
||||
emoji="✅"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdResolveConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.RESOLVE_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="isResolved"
|
||||
class-names="resolve"
|
||||
color-scheme="warning"
|
||||
icon="arrow-redo"
|
||||
emoji="👀"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.REOPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="showOpenButton"
|
||||
class-names="resolve"
|
||||
color-scheme="primary"
|
||||
icon="person"
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ $t('CONVERSATION.HEADER.OPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showAdditionalActions"
|
||||
ref="arrowDownButton"
|
||||
:color-scheme="buttonClass"
|
||||
:disabled="isLoading"
|
||||
icon="chevron-down"
|
||||
emoji="🔽"
|
||||
@click="openDropdown"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showActionsDropdown"
|
||||
v-on-clickaway="closeDropdown"
|
||||
class="dropdown-pane dropdown-pane--open"
|
||||
>
|
||||
<woot-dropdown-menu class="mb-0">
|
||||
<woot-dropdown-item v-if="!isPending">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
size="small"
|
||||
icon="snooze"
|
||||
@click="() => openSnoozeModal()"
|
||||
>
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-item v-if="!isPending">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
size="small"
|
||||
icon="book-clock"
|
||||
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
|
||||
>
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
</woot-dropdown-menu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.dropdown-pane {
|
||||
@apply left-auto top-[2.625rem] mt-0.5 right-0 max-w-[12.5rem] min-w-[9.75rem];
|
||||
|
||||
@@ -1,50 +1,3 @@
|
||||
<template>
|
||||
<woot-dropdown-menu>
|
||||
<woot-dropdown-header :title="$t('SIDEBAR.SET_AVAILABILITY_TITLE')" />
|
||||
<woot-dropdown-item
|
||||
v-for="status in availabilityStatuses"
|
||||
:key="status.value"
|
||||
class="flex items-baseline"
|
||||
>
|
||||
<woot-button
|
||||
size="small"
|
||||
:color-scheme="status.disabled ? '' : 'secondary'"
|
||||
:variant="status.disabled ? 'smooth' : 'clear'"
|
||||
class-names="status-change--dropdown-button"
|
||||
@click="changeAvailabilityStatus(status.value)"
|
||||
>
|
||||
<availability-status-badge :status="status.value" />
|
||||
{{ status.label }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
<woot-dropdown-item class="flex items-center justify-between p-2 m-0">
|
||||
<div class="flex items-center">
|
||||
<fluent-icon
|
||||
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
|
||||
icon="info"
|
||||
size="14"
|
||||
class="mt-px"
|
||||
/>
|
||||
|
||||
<span
|
||||
class="mx-1 my-0 text-xs font-medium text-slate-600 dark:text-slate-100"
|
||||
>
|
||||
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<woot-switch
|
||||
size="small"
|
||||
class="mx-1 mt-px mb-0"
|
||||
:value="currentUserAutoOffline"
|
||||
@input="updateAutoOffline"
|
||||
/>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
</woot-dropdown-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
@@ -136,3 +89,50 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-dropdown-menu>
|
||||
<woot-dropdown-header :title="$t('SIDEBAR.SET_AVAILABILITY_TITLE')" />
|
||||
<woot-dropdown-item
|
||||
v-for="status in availabilityStatuses"
|
||||
:key="status.value"
|
||||
class="flex items-baseline"
|
||||
>
|
||||
<woot-button
|
||||
size="small"
|
||||
:color-scheme="status.disabled ? '' : 'secondary'"
|
||||
:variant="status.disabled ? 'smooth' : 'clear'"
|
||||
class-names="status-change--dropdown-button"
|
||||
@click="changeAvailabilityStatus(status.value)"
|
||||
>
|
||||
<availability-status-badge :status="status.value" />
|
||||
{{ status.label }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
<woot-dropdown-item class="flex items-center justify-between p-2 m-0">
|
||||
<div class="flex items-center">
|
||||
<fluent-icon
|
||||
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
|
||||
icon="info"
|
||||
size="14"
|
||||
class="mt-px"
|
||||
/>
|
||||
|
||||
<span
|
||||
class="mx-1 my-0 text-xs font-medium text-slate-600 dark:text-slate-100"
|
||||
>
|
||||
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<woot-switch
|
||||
size="small"
|
||||
class="mx-1 mt-px mb-0"
|
||||
:value="currentUserAutoOffline"
|
||||
@input="updateAutoOffline"
|
||||
/>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
</woot-dropdown-menu>
|
||||
</template>
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
<template>
|
||||
<aside class="flex h-full">
|
||||
<primary-sidebar
|
||||
:logo-source="globalConfig.logoThumbnail"
|
||||
:installation-name="globalConfig.installationName"
|
||||
:is-a-custom-branded-instance="isACustomBrandedInstance"
|
||||
:account-id="accountId"
|
||||
:menu-items="primaryMenuItems"
|
||||
:active-menu-item="activePrimaryMenu.key"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@open-notification-panel="openNotificationPanel"
|
||||
/>
|
||||
<secondary-sidebar
|
||||
v-if="showSecondarySidebar"
|
||||
:class="sidebarClassName"
|
||||
:account-id="accountId"
|
||||
:inboxes="inboxes"
|
||||
:labels="labels"
|
||||
:teams="teams"
|
||||
:custom-views="customViews"
|
||||
:menu-config="activeSecondaryMenu"
|
||||
:current-user="currentUser"
|
||||
:is-on-chatwoot-cloud="isOnChatwootCloud"
|
||||
@add-label="showAddLabelPopup"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
/>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getSidebarItems } from './config/default-sidebar';
|
||||
@@ -209,3 +179,33 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="flex h-full">
|
||||
<primary-sidebar
|
||||
:logo-source="globalConfig.logoThumbnail"
|
||||
:installation-name="globalConfig.installationName"
|
||||
:is-a-custom-branded-instance="isACustomBrandedInstance"
|
||||
:account-id="accountId"
|
||||
:menu-items="primaryMenuItems"
|
||||
:active-menu-item="activePrimaryMenu.key"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@open-notification-panel="openNotificationPanel"
|
||||
/>
|
||||
<secondary-sidebar
|
||||
v-if="showSecondarySidebar"
|
||||
:class="sidebarClassName"
|
||||
:account-id="accountId"
|
||||
:inboxes="inboxes"
|
||||
:labels="labels"
|
||||
:teams="teams"
|
||||
:custom-views="customViews"
|
||||
:menu-config="activeSecondaryMenu"
|
||||
:current-user="currentUser"
|
||||
:is-on-chatwoot-cloud="isOnChatwootCloud"
|
||||
@add-label="showAddLabelPopup"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
/>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { showSwitchButton: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
account: 'getCurrentAccount',
|
||||
userAccounts: 'getUserAccounts',
|
||||
}),
|
||||
showShowCurrentAccountContext() {
|
||||
return this.userAccounts.length > 1 && this.account.name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setShowSwitch() {
|
||||
this.showSwitchButton = true;
|
||||
},
|
||||
resetShowSwitch() {
|
||||
this.showSwitchButton = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="showShowCurrentAccountContext"
|
||||
@@ -30,32 +56,6 @@
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { showSwitchButton: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
account: 'getCurrentAccount',
|
||||
userAccounts: 'getUserAccounts',
|
||||
}),
|
||||
showShowCurrentAccountContext() {
|
||||
return this.userAccounts.length > 1 && this.account.name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setShowSwitch() {
|
||||
this.showSwitchButton = true;
|
||||
},
|
||||
resetShowSwitch() {
|
||||
this.showSwitchButton = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
props: {
|
||||
showAccountModal: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
onChangeAccount(accountId) {
|
||||
const accountUrl = `/app/accounts/${accountId}/dashboard`;
|
||||
window.location.href = accountUrl;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-modal
|
||||
:show="showAccountModal"
|
||||
@@ -56,29 +82,3 @@
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
props: {
|
||||
showAccountModal: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
onChangeAccount(accountId) {
|
||||
const accountUrl = `/app/accounts/${accountId}/dashboard`;
|
||||
window.location.href = accountUrl;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,53 +1,3 @@
|
||||
<template>
|
||||
<woot-modal
|
||||
:show="show"
|
||||
:on-close="() => $emit('close-account-create-modal')"
|
||||
>
|
||||
<div class="h-auto overflow-auto flex flex-col">
|
||||
<woot-modal-header
|
||||
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
|
||||
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
|
||||
/>
|
||||
<div v-if="!hasAccounts" class="text-sm mt-6 mx-8 mb-0">
|
||||
<div class="items-center rounded-md flex alert">
|
||||
<div class="ml-1 mr-3">
|
||||
<fluent-icon icon="warning" />
|
||||
</div>
|
||||
{{ $t('CREATE_ACCOUNT.NO_ACCOUNT_WARNING') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col w-full" @submit.prevent="addAccount">
|
||||
<div class="w-full">
|
||||
<label :class="{ error: v$.accountName.$error }">
|
||||
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="accountName"
|
||||
type="text"
|
||||
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.accountName.$touch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="w-full">
|
||||
<woot-submit-button
|
||||
:disabled="
|
||||
v$.accountName.$invalid ||
|
||||
v$.accountName.$invalid ||
|
||||
uiFlags.isCreating
|
||||
"
|
||||
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
||||
:loading="uiFlags.isCreating"
|
||||
button-class="large expanded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
@@ -104,3 +54,53 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-modal
|
||||
:show="show"
|
||||
:on-close="() => $emit('close-account-create-modal')"
|
||||
>
|
||||
<div class="h-auto overflow-auto flex flex-col">
|
||||
<woot-modal-header
|
||||
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
|
||||
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
|
||||
/>
|
||||
<div v-if="!hasAccounts" class="text-sm mt-6 mx-8 mb-0">
|
||||
<div class="items-center rounded-md flex alert">
|
||||
<div class="ml-1 mr-3">
|
||||
<fluent-icon icon="warning" />
|
||||
</div>
|
||||
{{ $t('CREATE_ACCOUNT.NO_ACCOUNT_WARNING') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col w-full" @submit.prevent="addAccount">
|
||||
<div class="w-full">
|
||||
<label :class="{ error: v$.accountName.$error }">
|
||||
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="accountName"
|
||||
type="text"
|
||||
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.accountName.$touch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="w-full">
|
||||
<woot-submit-button
|
||||
:disabled="
|
||||
v$.accountName.$invalid ||
|
||||
v$.accountName.$invalid ||
|
||||
uiFlags.isCreating
|
||||
"
|
||||
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
||||
:loading="uiFlags.isCreating"
|
||||
button-class="large expanded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<template>
|
||||
<woot-button
|
||||
v-tooltip.right="$t(`SIDEBAR.PROFILE_SETTINGS`)"
|
||||
variant="link"
|
||||
class="items-center flex rounded-full"
|
||||
@click="handleClick"
|
||||
>
|
||||
<thumbnail
|
||||
:src="currentUser.avatar_url"
|
||||
:username="currentUser.name"
|
||||
:status="statusOfAgent"
|
||||
should-show-status-always
|
||||
size="32px"
|
||||
/>
|
||||
</woot-button>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from '../../widgets/Thumbnail.vue';
|
||||
@@ -38,3 +22,19 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<woot-button
|
||||
v-tooltip.right="$t(`SIDEBAR.PROFILE_SETTINGS`)"
|
||||
variant="link"
|
||||
class="items-center flex rounded-full"
|
||||
@click="handleClick"
|
||||
>
|
||||
<thumbnail
|
||||
:src="currentUser.avatar_url"
|
||||
:username="currentUser.name"
|
||||
:status="statusOfAgent"
|
||||
should-show-status-always
|
||||
size="32px"
|
||||
/>
|
||||
</woot-button>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div class="w-8 h-8">
|
||||
<router-link :to="dashboardPath" replace>
|
||||
<img :src="source" :alt="name" />
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
|
||||
@@ -30,3 +23,10 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-8 h-8">
|
||||
<router-link :to="dashboardPath" replace>
|
||||
<img :src="source" :alt="name" />
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<button
|
||||
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 p-0 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isNotificationPanelActive,
|
||||
}"
|
||||
@click="openNotificationPanel"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="alert"
|
||||
:class="{
|
||||
'text-woot-500': isNotificationPanelActive,
|
||||
}"
|
||||
/>
|
||||
<span
|
||||
v-if="unreadCount"
|
||||
class="text-black-900 bg-yellow-300 absolute -top-0.5 -right-1 text-xxs min-w-[1rem] rounded-full"
|
||||
>
|
||||
{{ unreadCount }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
@@ -54,3 +29,28 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<button
|
||||
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 p-0 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isNotificationPanelActive,
|
||||
}"
|
||||
@click="openNotificationPanel"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="alert"
|
||||
:class="{
|
||||
'text-woot-500': isNotificationPanelActive,
|
||||
}"
|
||||
/>
|
||||
<span
|
||||
v-if="unreadCount"
|
||||
class="text-black-900 bg-yellow-300 absolute -top-0.5 -right-1 text-xxs min-w-[1rem] rounded-full"
|
||||
>
|
||||
{{ unreadCount }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Auth from '../../../api/auth';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDropdownMenu,
|
||||
WootDropdownItem,
|
||||
AvailabilityStatus,
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
showChangeAccountOption() {
|
||||
if (this.globalConfig.createNewAccountFromDashboard) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { accounts = [] } = this.currentUser;
|
||||
return accounts.length > 1;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleProfileSettingClick(e, navigate) {
|
||||
this.$emit('close');
|
||||
navigate(e);
|
||||
},
|
||||
handleKeyboardHelpClick() {
|
||||
this.$emit('key-shortcut-modal');
|
||||
this.$emit('close');
|
||||
},
|
||||
logout() {
|
||||
Auth.logout();
|
||||
},
|
||||
onClickAway() {
|
||||
if (this.show) this.$emit('close');
|
||||
},
|
||||
openAppearanceOptions() {
|
||||
const ninja = document.querySelector('ninja-keys');
|
||||
ninja.open({ parent: 'appearance_settings' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="menu-slide">
|
||||
<div
|
||||
@@ -104,60 +161,3 @@
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Auth from '../../../api/auth';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDropdownMenu,
|
||||
WootDropdownItem,
|
||||
AvailabilityStatus,
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
showChangeAccountOption() {
|
||||
if (this.globalConfig.createNewAccountFromDashboard) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { accounts = [] } = this.currentUser;
|
||||
return accounts.length > 1;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleProfileSettingClick(e, navigate) {
|
||||
this.$emit('close');
|
||||
navigate(e);
|
||||
},
|
||||
handleKeyboardHelpClick() {
|
||||
this.$emit('key-shortcut-modal');
|
||||
this.$emit('close');
|
||||
},
|
||||
logout() {
|
||||
Auth.logout();
|
||||
},
|
||||
onClickAway() {
|
||||
if (this.show) this.$emit('close');
|
||||
},
|
||||
openAppearanceOptions() {
|
||||
const ninja = document.querySelector('ninja-keys');
|
||||
ninja.open({ parent: 'appearance_settings' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-800/50 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<logo
|
||||
:source="logoSource"
|
||||
:name="installationName"
|
||||
:account-id="accountId"
|
||||
class="m-4 mb-10"
|
||||
/>
|
||||
<primary-nav-item
|
||||
v-for="menuItem in menuItems"
|
||||
:key="menuItem.toState"
|
||||
:icon="menuItem.icon"
|
||||
:name="menuItem.label"
|
||||
:to="menuItem.toState"
|
||||
:is-child-menu-active="menuItem.key === activeMenuItem"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-end pb-6">
|
||||
<primary-nav-item
|
||||
v-if="!isACustomBrandedInstance"
|
||||
icon="book-open-globe"
|
||||
name="DOCS"
|
||||
:open-in-new-page="true"
|
||||
:to="helpDocsURL"
|
||||
/>
|
||||
<notification-bell @open-notification-panel="openNotificationPanel" />
|
||||
<agent-details @toggle-menu="toggleOptions" />
|
||||
<options-menu
|
||||
:show="showOptionsMenu"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@show-support-chat-window="toggleSupportChatWindow"
|
||||
@key-shortcut-modal="$emit('key-shortcut-modal')"
|
||||
@close="toggleOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Logo from './Logo.vue';
|
||||
import PrimaryNavItem from './PrimaryNavItem.vue';
|
||||
@@ -106,3 +66,43 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-800/50 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<logo
|
||||
:source="logoSource"
|
||||
:name="installationName"
|
||||
:account-id="accountId"
|
||||
class="m-4 mb-10"
|
||||
/>
|
||||
<primary-nav-item
|
||||
v-for="menuItem in menuItems"
|
||||
:key="menuItem.toState"
|
||||
:icon="menuItem.icon"
|
||||
:name="menuItem.label"
|
||||
:to="menuItem.toState"
|
||||
:is-child-menu-active="menuItem.key === activeMenuItem"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-end pb-6">
|
||||
<primary-nav-item
|
||||
v-if="!isACustomBrandedInstance"
|
||||
icon="book-open-globe"
|
||||
name="DOCS"
|
||||
:open-in-new-page="true"
|
||||
:to="helpDocsURL"
|
||||
/>
|
||||
<notification-bell @open-notification-panel="openNotificationPanel" />
|
||||
<agent-details @toggle-menu="toggleOptions" />
|
||||
<options-menu
|
||||
:show="showOptionsMenu"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@show-support-chat-window="toggleSupportChatWindow"
|
||||
@key-shortcut-modal="$emit('key-shortcut-modal')"
|
||||
@close="toggleOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
<template>
|
||||
<router-link v-slot="{ href, isActive, navigate }" :to="to" custom>
|
||||
<a
|
||||
v-tooltip.right="$t(`SIDEBAR.${name}`)"
|
||||
:href="href"
|
||||
class="text-slate-700 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isActive || isChildMenuActive,
|
||||
}"
|
||||
:rel="openInNewPage ? 'noopener noreferrer nofollow' : undefined"
|
||||
:target="openInNewPage ? '_blank' : undefined"
|
||||
@click="navigate"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="icon"
|
||||
:class="{
|
||||
'text-woot-500': isActive || isChildMenuActive,
|
||||
}"
|
||||
/>
|
||||
<span class="sr-only">{{ name }}</span>
|
||||
<span
|
||||
v-if="count"
|
||||
class="text-black-900 bg-yellow-500 absolute -top-1 -right-1"
|
||||
>
|
||||
{{ count }}
|
||||
</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -58,3 +28,33 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<router-link v-slot="{ href, isActive, navigate }" :to="to" custom>
|
||||
<a
|
||||
v-tooltip.right="$t(`SIDEBAR.${name}`)"
|
||||
:href="href"
|
||||
class="text-slate-700 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isActive || isChildMenuActive,
|
||||
}"
|
||||
:rel="openInNewPage ? 'noopener noreferrer nofollow' : undefined"
|
||||
:target="openInNewPage ? '_blank' : undefined"
|
||||
@click="navigate"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="icon"
|
||||
:class="{
|
||||
'text-woot-500': isActive || isChildMenuActive,
|
||||
}"
|
||||
/>
|
||||
<span class="sr-only">{{ name }}</span>
|
||||
<span
|
||||
v-if="count"
|
||||
class="text-black-900 bg-yellow-500 absolute -top-1 -right-1"
|
||||
>
|
||||
{{ count }}
|
||||
</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="hasSecondaryMenu"
|
||||
class="h-full overflow-auto w-48 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-800/50 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
|
||||
>
|
||||
<account-context @toggle-accounts="toggleAccountModal" />
|
||||
<transition-group
|
||||
name="menu-list"
|
||||
tag="ul"
|
||||
class="pt-2 list-none ml-0 mb-0"
|
||||
>
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in accessibleMenuItems"
|
||||
:key="menuItem.toState"
|
||||
:menu-item="menuItem"
|
||||
/>
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in additionalSecondaryMenuItems[menuConfig.parentNav]"
|
||||
:key="menuItem.key"
|
||||
:menu-item="menuItem"
|
||||
@add-label="showAddLabelPopup"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
import SecondaryNavItem from './SecondaryNavItem.vue';
|
||||
@@ -259,3 +234,28 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="hasSecondaryMenu"
|
||||
class="h-full overflow-auto w-48 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-800/50 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
|
||||
>
|
||||
<account-context @toggle-accounts="toggleAccountModal" />
|
||||
<transition-group
|
||||
name="menu-list"
|
||||
tag="ul"
|
||||
class="pt-2 list-none ml-0 mb-0"
|
||||
>
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in accessibleMenuItems"
|
||||
:key="menuItem.toState"
|
||||
:menu-item="menuItem"
|
||||
/>
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in additionalSecondaryMenuItems[menuConfig.parentNav]"
|
||||
:key="menuItem.key"
|
||||
:menu-item="menuItem"
|
||||
@add-label="showAddLabelPopup"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+51
-51
@@ -1,3 +1,54 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
shouldTruncate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
warningIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showChildCount: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
childItemCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showIcon() {
|
||||
return {
|
||||
'overflow-hidden whitespace-nowrap text-ellipsis': this.shouldTruncate,
|
||||
};
|
||||
},
|
||||
isCountZero() {
|
||||
return this.childItemCount === 0;
|
||||
},
|
||||
menuTitle() {
|
||||
return this.shouldTruncate ? this.label : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
@@ -78,54 +129,3 @@
|
||||
</li>
|
||||
</router-link>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
shouldTruncate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
warningIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showChildCount: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
childItemCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showIcon() {
|
||||
return {
|
||||
'overflow-hidden whitespace-nowrap text-ellipsis': this.shouldTruncate,
|
||||
};
|
||||
},
|
||||
isCountZero() {
|
||||
return this.childItemCount === 0;
|
||||
},
|
||||
menuTitle() {
|
||||
return this.shouldTruncate ? this.label : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,97 +1,3 @@
|
||||
<template>
|
||||
<li v-show="isMenuItemVisible" class="mt-1">
|
||||
<div v-if="hasSubMenu" class="flex justify-between">
|
||||
<span
|
||||
class="px-2 pt-1 my-2 text-sm font-semibold text-slate-700 dark:text-slate-200"
|
||||
>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
</span>
|
||||
<div v-if="menuItem.showNewButton" class="flex items-center">
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="add"
|
||||
class="p-0 ml-2"
|
||||
@click="onClickOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
v-else
|
||||
class="flex items-center p-2 m-0 text-sm font-medium leading-4 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
||||
:class="computedClass"
|
||||
:to="menuItem && menuItem.toState"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="menuItem.icon"
|
||||
class="min-w-[1rem] mr-1.5 rtl:mr-0 rtl:ml-1.5"
|
||||
size="14"
|
||||
/>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
<span
|
||||
v-if="showChildCount(menuItem.count)"
|
||||
class="px-1 py-0 mx-1 font-medium rounded-md text-xxs"
|
||||
:class="{
|
||||
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
||||
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
||||
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600':
|
||||
isActiveView,
|
||||
'bg-slate-50 dark:bg-slate-700': !isActiveView,
|
||||
}"
|
||||
>
|
||||
{{ `${menuItem.count}` }}
|
||||
</span>
|
||||
<span
|
||||
v-if="menuItem.beta"
|
||||
data-view-component="true"
|
||||
label="Beta"
|
||||
class="inline-block px-1 mx-1 font-medium leading-4 text-green-500 border border-green-400 rounded-lg text-xxs"
|
||||
>
|
||||
{{ $t('SIDEBAR.BETA') }}
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<ul v-if="hasSubMenu" class="mb-0 ml-0 list-none">
|
||||
<secondary-child-nav-item
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.id"
|
||||
:to="child.toState"
|
||||
:label="child.label"
|
||||
:label-color="child.color"
|
||||
:should-truncate="child.truncateLabel"
|
||||
:icon="computedInboxClass(child)"
|
||||
:warning-icon="computedInboxErrorClass(child)"
|
||||
:show-child-count="showChildCount(child.count)"
|
||||
:child-item-count="child.count"
|
||||
/>
|
||||
<Policy :permissions="['administrator']">
|
||||
<router-link
|
||||
v-if="menuItem.newLink"
|
||||
v-slot="{ href, navigate }"
|
||||
:to="menuItem.toState"
|
||||
custom
|
||||
>
|
||||
<li class="pl-1">
|
||||
<a :href="href">
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="add"
|
||||
:data-testid="menuItem.dataTestid"
|
||||
@click="e => newLinkClick(e, navigate)"
|
||||
>
|
||||
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
|
||||
</woot-button>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</Policy>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
@@ -279,3 +185,97 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li v-show="isMenuItemVisible" class="mt-1">
|
||||
<div v-if="hasSubMenu" class="flex justify-between">
|
||||
<span
|
||||
class="px-2 pt-1 my-2 text-sm font-semibold text-slate-700 dark:text-slate-200"
|
||||
>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
</span>
|
||||
<div v-if="menuItem.showNewButton" class="flex items-center">
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="add"
|
||||
class="p-0 ml-2"
|
||||
@click="onClickOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
v-else
|
||||
class="flex items-center p-2 m-0 text-sm font-medium leading-4 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
||||
:class="computedClass"
|
||||
:to="menuItem && menuItem.toState"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="menuItem.icon"
|
||||
class="min-w-[1rem] mr-1.5 rtl:mr-0 rtl:ml-1.5"
|
||||
size="14"
|
||||
/>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
<span
|
||||
v-if="showChildCount(menuItem.count)"
|
||||
class="px-1 py-0 mx-1 font-medium rounded-md text-xxs"
|
||||
:class="{
|
||||
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
||||
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
||||
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600':
|
||||
isActiveView,
|
||||
'bg-slate-50 dark:bg-slate-700': !isActiveView,
|
||||
}"
|
||||
>
|
||||
{{ `${menuItem.count}` }}
|
||||
</span>
|
||||
<span
|
||||
v-if="menuItem.beta"
|
||||
data-view-component="true"
|
||||
label="Beta"
|
||||
class="inline-block px-1 mx-1 font-medium leading-4 text-green-500 border border-green-400 rounded-lg text-xxs"
|
||||
>
|
||||
{{ $t('SIDEBAR.BETA') }}
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<ul v-if="hasSubMenu" class="mb-0 ml-0 list-none">
|
||||
<secondary-child-nav-item
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.id"
|
||||
:to="child.toState"
|
||||
:label="child.label"
|
||||
:label-color="child.color"
|
||||
:should-truncate="child.truncateLabel"
|
||||
:icon="computedInboxClass(child)"
|
||||
:warning-icon="computedInboxErrorClass(child)"
|
||||
:show-child-count="showChildCount(child.count)"
|
||||
:child-item-count="child.count"
|
||||
/>
|
||||
<Policy :permissions="['administrator']">
|
||||
<router-link
|
||||
v-if="menuItem.newLink"
|
||||
v-slot="{ href, navigate }"
|
||||
:to="menuItem.toState"
|
||||
custom
|
||||
>
|
||||
<li class="pl-1">
|
||||
<a :href="href">
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="add"
|
||||
:data-testid="menuItem.dataTestid"
|
||||
@click="e => newLinkClick(e, navigate)"
|
||||
>
|
||||
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
|
||||
</woot-button>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</Policy>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
<template>
|
||||
<div class="announcement-popup">
|
||||
<span v-if="popupMessage" class="popup-content">
|
||||
{{ popupMessage }}
|
||||
<span v-if="routeText" class="route-url" @click="onClickOpenPath">
|
||||
{{ routeText }}
|
||||
</span>
|
||||
</span>
|
||||
<div v-if="hasCloseButton" class="popup-close">
|
||||
<woot-button
|
||||
v-if="hasCloseButton"
|
||||
color-scheme="primary"
|
||||
variant="link"
|
||||
size="small"
|
||||
@click="onClickClose"
|
||||
>
|
||||
{{ closeButtonText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -49,6 +28,27 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="announcement-popup">
|
||||
<span v-if="popupMessage" class="popup-content">
|
||||
{{ popupMessage }}
|
||||
<span v-if="routeText" class="route-url" @click="onClickOpenPath">
|
||||
{{ routeText }}
|
||||
</span>
|
||||
</span>
|
||||
<div v-if="hasCloseButton" class="popup-close">
|
||||
<woot-button
|
||||
v-if="hasCloseButton"
|
||||
color-scheme="primary"
|
||||
variant="link"
|
||||
size="small"
|
||||
@click="onClickClose"
|
||||
>
|
||||
{{ closeButtonText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.announcement-popup {
|
||||
|
||||
@@ -1,45 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center h-12 gap-4 px-4 py-3 text-xs text-white banner dark:text-white"
|
||||
:class="bannerClasses"
|
||||
>
|
||||
<span class="banner-message">
|
||||
{{ bannerMessage }}
|
||||
<a
|
||||
v-if="hrefLink"
|
||||
:href="hrefLink"
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
{{ hrefLinkText }}
|
||||
</a>
|
||||
</span>
|
||||
<div class="actions">
|
||||
<woot-button
|
||||
v-if="hasActionButton"
|
||||
size="tiny"
|
||||
:icon="actionButtonIcon"
|
||||
:variant="actionButtonVariant"
|
||||
color-scheme="primary"
|
||||
class-names="banner-action__button"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ actionButtonLabel }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="hasCloseButton"
|
||||
size="tiny"
|
||||
:color-scheme="colorScheme"
|
||||
icon="dismiss-circle"
|
||||
class-names="banner-action__button"
|
||||
@click="onClickClose"
|
||||
>
|
||||
{{ $t('GENERAL_SETTINGS.DISMISS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -101,6 +59,48 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center h-12 gap-4 px-4 py-3 text-xs text-white banner dark:text-white"
|
||||
:class="bannerClasses"
|
||||
>
|
||||
<span class="banner-message">
|
||||
{{ bannerMessage }}
|
||||
<a
|
||||
v-if="hrefLink"
|
||||
:href="hrefLink"
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
{{ hrefLinkText }}
|
||||
</a>
|
||||
</span>
|
||||
<div class="actions">
|
||||
<woot-button
|
||||
v-if="hasActionButton"
|
||||
size="tiny"
|
||||
:icon="actionButtonIcon"
|
||||
:variant="actionButtonVariant"
|
||||
color-scheme="primary"
|
||||
class-names="banner-action__button"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ actionButtonLabel }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="hasCloseButton"
|
||||
size="tiny"
|
||||
:color-scheme="colorScheme"
|
||||
icon="dismiss-circle"
|
||||
class-names="banner-action__button"
|
||||
@click="onClickClose"
|
||||
>
|
||||
{{ $t('GENERAL_SETTINGS.DISMISS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner {
|
||||
&.primary {
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="fixed outline-none z-[9999] cursor-pointer"
|
||||
:style="style"
|
||||
tabindex="0"
|
||||
@blur="$emit('close')"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -40,3 +30,13 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="fixed outline-none z-[9999] cursor-pointer"
|
||||
:style="style"
|
||||
tabindex="0"
|
||||
@blur="$emit('close')"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
<template>
|
||||
<div class="date-picker">
|
||||
<date-picker
|
||||
:range="true"
|
||||
:confirm="true"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
:confirm-text="confirmText"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
export default {
|
||||
@@ -38,3 +23,18 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="date-picker">
|
||||
<date-picker
|
||||
:range="true"
|
||||
:confirm="true"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
:confirm-text="confirmText"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<template>
|
||||
<div class="date-picker">
|
||||
<date-picker
|
||||
type="datetime"
|
||||
:confirm="true"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
:confirm-text="confirmText"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
:disabled-date="disableBeforeToday"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addDays from 'date-fns/addDays';
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
@@ -45,3 +29,19 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="date-picker">
|
||||
<date-picker
|
||||
type="datetime"
|
||||
:confirm="true"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
:confirm-text="confirmText"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
:disabled-date="disableBeforeToday"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,32 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex ltr:mr-1 rtl:ml-1 mb-1"
|
||||
:class="labelClass"
|
||||
:style="labelStyle"
|
||||
:title="description"
|
||||
>
|
||||
<span v-if="icon" class="label-action--button">
|
||||
<fluent-icon :icon="icon" size="12" class="label--icon cursor-pointer" />
|
||||
</span>
|
||||
<span
|
||||
v-if="['smooth', 'dashed'].includes(variant) && title && !icon"
|
||||
:style="{ background: color }"
|
||||
class="label-color-dot flex-shrink-0"
|
||||
/>
|
||||
<span v-if="!href" class="whitespace-nowrap text-ellipsis overflow-hidden">
|
||||
{{ title }}
|
||||
</span>
|
||||
<a v-else :href="href" :style="anchorStyle">{{ title }}</a>
|
||||
<button
|
||||
v-if="showClose"
|
||||
class="label-close--button p-0"
|
||||
:style="{ color: textColor }"
|
||||
@click="onClick"
|
||||
>
|
||||
<fluent-icon icon="dismiss" size="12" class="close--icon" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
|
||||
@@ -108,6 +79,35 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex ltr:mr-1 rtl:ml-1 mb-1"
|
||||
:class="labelClass"
|
||||
:style="labelStyle"
|
||||
:title="description"
|
||||
>
|
||||
<span v-if="icon" class="label-action--button">
|
||||
<fluent-icon :icon="icon" size="12" class="label--icon cursor-pointer" />
|
||||
</span>
|
||||
<span
|
||||
v-if="['smooth', 'dashed'].includes(variant) && title && !icon"
|
||||
:style="{ background: color }"
|
||||
class="label-color-dot flex-shrink-0"
|
||||
/>
|
||||
<span v-if="!href" class="whitespace-nowrap text-ellipsis overflow-hidden">
|
||||
{{ title }}
|
||||
</span>
|
||||
<a v-else :href="href" :style="anchorStyle">{{ title }}</a>
|
||||
<button
|
||||
v-if="showClose"
|
||||
class="label-close--button p-0"
|
||||
:style="{ color: textColor }"
|
||||
@click="onClick"
|
||||
>
|
||||
<fluent-icon icon="dismiss" size="12" class="close--icon" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.label {
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
heading: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: 'Active',
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col min-w-[15rem] max-h-[21.25rem] max-w-[23.75rem] rounded-md border border-solid border-slate-75 dark:border-slate-600"
|
||||
@@ -41,30 +68,3 @@
|
||||
<slot v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
heading: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: 'Active',
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button p-0"
|
||||
:class="{ active: value, small: size === 'small' }"
|
||||
role="switch"
|
||||
:aria-checked="value.toString()"
|
||||
@click="onClick"
|
||||
>
|
||||
<span aria-hidden="true" :class="{ active: value }" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -24,6 +11,19 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button p-0"
|
||||
:class="{ active: value, small: size === 'small' }"
|
||||
role="switch"
|
||||
:aria-checked="value.toString()"
|
||||
@click="onClick"
|
||||
>
|
||||
<span aria-hidden="true" :class="{ active: value }" />
|
||||
</button>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.toggle-button {
|
||||
@apply bg-slate-200 dark:bg-slate-600;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<template>
|
||||
<li
|
||||
:class="{
|
||||
'tabs-title': true,
|
||||
'is-active': active,
|
||||
}"
|
||||
>
|
||||
<a @click="onTabClick">
|
||||
{{ name }}
|
||||
<div v-if="showBadge" class="badge min-w-[20px]">
|
||||
<span>
|
||||
{{ getItemCount }}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'WootTabsItem',
|
||||
@@ -61,3 +44,20 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<li
|
||||
:class="{
|
||||
'tabs-title': true,
|
||||
'is-active': active,
|
||||
}"
|
||||
>
|
||||
<a @click="onTabClick">
|
||||
{{ name }}
|
||||
<div v-if="showBadge" class="badge min-w-[20px]">
|
||||
<span>
|
||||
{{ getItemCount }}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
v-tooltip.top="{
|
||||
content: tooltipText,
|
||||
delay: { show: 1500, hide: 0 },
|
||||
hideOnClick: true,
|
||||
}"
|
||||
class="ml-auto leading-4 text-xxs text-slate-500 dark:text-slate-500 hover:text-slate-900 dark:hover:text-slate-100"
|
||||
>
|
||||
<span>{{ `${createdAtTime} • ${lastActivityTime}` }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const MINUTE_IN_MILLI_SECONDS = 60000;
|
||||
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
|
||||
@@ -118,3 +105,16 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-tooltip.top="{
|
||||
content: tooltipText,
|
||||
delay: { show: 1500, hide: 0 },
|
||||
hideOnClick: true,
|
||||
}"
|
||||
class="ml-auto leading-4 text-xxs text-slate-500 dark:text-slate-500 hover:text-slate-900 dark:hover:text-slate-100"
|
||||
>
|
||||
<span>{{ `${createdAtTime} • ${lastActivityTime}` }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
|
||||
export default {
|
||||
mixins: [globalConfigMixin],
|
||||
props: {
|
||||
isFullwidth: Boolean,
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
classObject() {
|
||||
return 'w-full';
|
||||
},
|
||||
activeIndex() {
|
||||
return this.items.findIndex(i => i.route === this.$route.name);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(item) {
|
||||
return this.items.indexOf(item) === this.activeIndex;
|
||||
},
|
||||
isOver(item) {
|
||||
return this.items.indexOf(item) < this.activeIndex;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition-group
|
||||
name="wizard-items"
|
||||
@@ -33,38 +65,6 @@
|
||||
</div>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
|
||||
export default {
|
||||
mixins: [globalConfigMixin],
|
||||
props: {
|
||||
isFullwidth: Boolean,
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
classObject() {
|
||||
return 'w-full';
|
||||
},
|
||||
activeIndex() {
|
||||
return this.items.findIndex(i => i.route === this.$route.name);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(item) {
|
||||
return this.items.indexOf(item) === this.activeIndex;
|
||||
},
|
||||
isOver(item) {
|
||||
return this.items.indexOf(item) < this.activeIndex;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.wizard-box {
|
||||
.item {
|
||||
|
||||
@@ -1,32 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
class="button"
|
||||
:type="type"
|
||||
:class="buttonClasses"
|
||||
:disabled="isDisabled || isLoading"
|
||||
@click="handleClick"
|
||||
>
|
||||
<spinner
|
||||
v-if="isLoading"
|
||||
size="small"
|
||||
:color-scheme="showDarkSpinner ? 'dark' : ''"
|
||||
/>
|
||||
<emoji-or-icon
|
||||
v-else-if="icon || emoji"
|
||||
class="icon"
|
||||
:emoji="emoji"
|
||||
:icon="icon"
|
||||
:icon-size="iconSize"
|
||||
/>
|
||||
<span
|
||||
v-if="$slots.default"
|
||||
class="button__content"
|
||||
:class="{ 'text-left rtl:text-right': size !== 'expanded' }"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||
@@ -132,3 +103,32 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="button"
|
||||
:type="type"
|
||||
:class="buttonClasses"
|
||||
:disabled="isDisabled || isLoading"
|
||||
@click="handleClick"
|
||||
>
|
||||
<spinner
|
||||
v-if="isLoading"
|
||||
size="small"
|
||||
:color-scheme="showDarkSpinner ? 'dark' : ''"
|
||||
/>
|
||||
<emoji-or-icon
|
||||
v-else-if="icon || emoji"
|
||||
class="icon"
|
||||
:emoji="emoji"
|
||||
:icon="icon"
|
||||
:icon-size="iconSize"
|
||||
/>
|
||||
<span
|
||||
v-if="$slots.default"
|
||||
class="button__content"
|
||||
:class="{ 'text-left rtl:text-right': size !== 'expanded' }"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,38 +1,3 @@
|
||||
<template>
|
||||
<div v-if="!isFetchingAppIntegrations">
|
||||
<div v-if="isAIIntegrationEnabled" class="relative">
|
||||
<AIAssistanceCTAButton
|
||||
v-if="shouldShowAIAssistCTAButton"
|
||||
@click="openAIAssist"
|
||||
/>
|
||||
<woot-button
|
||||
v-else
|
||||
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
|
||||
icon="wand"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="openAIAssist"
|
||||
/>
|
||||
<woot-modal
|
||||
:show.sync="showAIAssistanceModal"
|
||||
:on-close="hideAIAssistanceModal"
|
||||
>
|
||||
<AIAssistanceModal
|
||||
:ai-option="aiOption"
|
||||
@apply-text="insertText"
|
||||
@close="hideAIAssistanceModal"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
<div v-else-if="shouldShowAIAssistCTAButtonForAdmin" class="relative">
|
||||
<AIAssistanceCTAButton @click="openAICta" />
|
||||
<woot-modal :show.sync="showAICtaModal" :on-close="hideAICtaModal">
|
||||
<AICTAModal @close="hideAICtaModal" />
|
||||
</woot-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
@@ -141,3 +106,38 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="!isFetchingAppIntegrations">
|
||||
<div v-if="isAIIntegrationEnabled" class="relative">
|
||||
<AIAssistanceCTAButton
|
||||
v-if="shouldShowAIAssistCTAButton"
|
||||
@click="openAIAssist"
|
||||
/>
|
||||
<woot-button
|
||||
v-else
|
||||
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
|
||||
icon="wand"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="openAIAssist"
|
||||
/>
|
||||
<woot-modal
|
||||
:show.sync="showAIAssistanceModal"
|
||||
:on-close="hideAIAssistanceModal"
|
||||
>
|
||||
<AIAssistanceModal
|
||||
:ai-option="aiOption"
|
||||
@apply-text="insertText"
|
||||
@close="hideAIAssistanceModal"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
<div v-else-if="shouldShowAIAssistCTAButtonForAdmin" class="relative">
|
||||
<AIAssistanceCTAButton @click="openAICta" />
|
||||
<woot-modal :show.sync="showAICtaModal" :on-close="hideAICtaModal">
|
||||
<AICTAModal @close="hideAICtaModal" />
|
||||
</woot-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="relative">
|
||||
<woot-button
|
||||
@@ -19,15 +28,6 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
|
||||
@@ -1,42 +1,3 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<woot-modal-header :header-title="headerTitle" />
|
||||
<form
|
||||
class="modal-content flex flex-col w-full"
|
||||
@submit.prevent="applyText"
|
||||
>
|
||||
<div v-if="draftMessage" class="w-full">
|
||||
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.DRAFT_TITLE') }}
|
||||
</h4>
|
||||
<p v-dompurify-html="formatMessage(draftMessage, false)" />
|
||||
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.GENERATED_TITLE')
|
||||
}}
|
||||
</h4>
|
||||
</div>
|
||||
<div>
|
||||
<AILoader v-if="isGenerating" />
|
||||
<p v-else v-dompurify-html="formatMessage(generatedContent, false)" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.BUTTONS.CANCEL')
|
||||
}}
|
||||
</woot-button>
|
||||
<woot-button :disabled="!generatedContent">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.BUTTONS.APPLY')
|
||||
}}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
@@ -99,6 +60,45 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<woot-modal-header :header-title="headerTitle" />
|
||||
<form
|
||||
class="modal-content flex flex-col w-full"
|
||||
@submit.prevent="applyText"
|
||||
>
|
||||
<div v-if="draftMessage" class="w-full">
|
||||
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.DRAFT_TITLE') }}
|
||||
</h4>
|
||||
<p v-dompurify-html="formatMessage(draftMessage, false)" />
|
||||
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.GENERATED_TITLE')
|
||||
}}
|
||||
</h4>
|
||||
</div>
|
||||
<div>
|
||||
<AILoader v-if="isGenerating" />
|
||||
<p v-else v-dompurify-html="formatMessage(generatedContent, false)" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.BUTTONS.CANCEL')
|
||||
}}
|
||||
</woot-button>
|
||||
<woot-button :disabled="!generatedContent">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.BUTTONS.APPLY')
|
||||
}}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal-content {
|
||||
@apply pt-2 px-8 pb-8;
|
||||
|
||||
@@ -1,41 +1,3 @@
|
||||
<template>
|
||||
<div class="flex-1 min-w-0 px-0">
|
||||
<woot-modal-header
|
||||
:header-title="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.TITLE')"
|
||||
:header-content="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DESC')"
|
||||
/>
|
||||
<form
|
||||
class="flex flex-col flex-wrap modal-content"
|
||||
@submit.prevent="finishOpenAI"
|
||||
>
|
||||
<div class="w-full mt-2">
|
||||
<woot-input
|
||||
v-model="value"
|
||||
type="text"
|
||||
:class="{ error: v$.value.$error }"
|
||||
:placeholder="
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
|
||||
"
|
||||
@blur="v$.value.$touch"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between w-full gap-2 px-0 py-2">
|
||||
<woot-button variant="link" @click.prevent="openOpenAIDoc">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.NEED_HELP') }}
|
||||
</woot-button>
|
||||
<div class="flex items-center gap-1">
|
||||
<woot-button variant="clear" @click.prevent="onDismiss">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
|
||||
</woot-button>
|
||||
<woot-button :is-disabled="v$.value.$invalid">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
@@ -113,3 +75,41 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 min-w-0 px-0">
|
||||
<woot-modal-header
|
||||
:header-title="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.TITLE')"
|
||||
:header-content="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DESC')"
|
||||
/>
|
||||
<form
|
||||
class="flex flex-col flex-wrap modal-content"
|
||||
@submit.prevent="finishOpenAI"
|
||||
>
|
||||
<div class="w-full mt-2">
|
||||
<woot-input
|
||||
v-model="value"
|
||||
type="text"
|
||||
:class="{ error: v$.value.$error }"
|
||||
:placeholder="
|
||||
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
|
||||
"
|
||||
@blur="v$.value.$touch"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between w-full gap-2 px-0 py-2">
|
||||
<woot-button variant="link" @click.prevent="openOpenAIDoc">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.NEED_HELP') }}
|
||||
</woot-button>
|
||||
<div class="flex items-center gap-1">
|
||||
<woot-button variant="clear" @click.prevent="onDismiss">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
|
||||
</woot-button>
|
||||
<woot-button :is-disabled="v$.value.$invalid">
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<script></script>
|
||||
|
||||
<template>
|
||||
<div class="animation-container margin-top-1">
|
||||
<div class="ai-typing--wrap">
|
||||
@@ -12,8 +14,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.animation-container {
|
||||
position: relative;
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
<template>
|
||||
<div class="flex overflow-auto max-h-[12.5rem]">
|
||||
<div
|
||||
v-for="(attachment, index) in nonRecordedAudioAttachments"
|
||||
:key="attachment.id"
|
||||
class="preview-item flex items-center p-1 bg-slate-50 dark:bg-slate-800 gap-1 rounded-md w-[15rem] mb-1"
|
||||
>
|
||||
<div class="max-w-[4rem] flex-shrink-0 w-6 flex items-center">
|
||||
<img
|
||||
v-if="isTypeImage(attachment.resource)"
|
||||
class="object-cover w-6 h-6 rounded-sm"
|
||||
:src="attachment.thumb"
|
||||
/>
|
||||
<span v-else class="relative w-6 h-6 text-lg text-left -top-px">
|
||||
📄
|
||||
</span>
|
||||
</div>
|
||||
<div class="max-w-3/5 min-w-[50%] overflow-hidden text-ellipsis">
|
||||
<span
|
||||
class="h-4 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ fileName(attachment.resource) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-[30%] justify-center">
|
||||
<span class="overflow-hidden text-xs text-ellipsis whitespace-nowrap">
|
||||
{{ formatFileSize(attachment.resource) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<woot-button
|
||||
class="!w-6 !h-6 text-sm rounded-md hover:bg-slate-50 dark:hover:bg-slate-800 clear secondary"
|
||||
icon="dismiss"
|
||||
@click="onRemoveAttachment(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { formatBytes } from 'shared/helpers/FileHelper';
|
||||
@@ -82,3 +42,43 @@ const fileName = file => {
|
||||
return file.filename || file.name;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex overflow-auto max-h-[12.5rem]">
|
||||
<div
|
||||
v-for="(attachment, index) in nonRecordedAudioAttachments"
|
||||
:key="attachment.id"
|
||||
class="preview-item flex items-center p-1 bg-slate-50 dark:bg-slate-800 gap-1 rounded-md w-[15rem] mb-1"
|
||||
>
|
||||
<div class="max-w-[4rem] flex-shrink-0 w-6 flex items-center">
|
||||
<img
|
||||
v-if="isTypeImage(attachment.resource)"
|
||||
class="object-cover w-6 h-6 rounded-sm"
|
||||
:src="attachment.thumb"
|
||||
/>
|
||||
<span v-else class="relative w-6 h-6 text-lg text-left -top-px">
|
||||
📄
|
||||
</span>
|
||||
</div>
|
||||
<div class="max-w-3/5 min-w-[50%] overflow-hidden text-ellipsis">
|
||||
<span
|
||||
class="h-4 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ fileName(attachment.resource) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-[30%] justify-center">
|
||||
<span class="overflow-hidden text-xs text-ellipsis whitespace-nowrap">
|
||||
{{ formatFileSize(attachment.resource) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<woot-button
|
||||
class="!w-6 !h-6 text-sm rounded-md hover:bg-slate-50 dark:hover:bg-slate-800 clear secondary"
|
||||
icon="dismiss"
|
||||
@click="onRemoveAttachment(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,97 @@
|
||||
<script>
|
||||
import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput.vue';
|
||||
import AutomationActionFileInput from './AutomationFileInput.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
export default {
|
||||
components: {
|
||||
AutomationActionTeamMessageInput,
|
||||
AutomationActionFileInput,
|
||||
WootMessageEditor,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
actionTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dropdownValues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showActionInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
initialFileName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isMacro: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
action_name: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_name;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_name: value });
|
||||
},
|
||||
},
|
||||
action_params: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_params;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_params: value });
|
||||
},
|
||||
},
|
||||
inputType() {
|
||||
return this.actionTypes.find(action => action.key === this.action_name)
|
||||
.inputType;
|
||||
},
|
||||
actionInputStyles() {
|
||||
return {
|
||||
'has-error': this.errorMessage,
|
||||
'is-a-macro': this.isMacro,
|
||||
};
|
||||
},
|
||||
castMessageVmodel: {
|
||||
get() {
|
||||
if (Array.isArray(this.action_params)) {
|
||||
return this.action_params[0];
|
||||
}
|
||||
return this.action_params;
|
||||
},
|
||||
set(value) {
|
||||
this.action_params = value;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeAction() {
|
||||
this.$emit('removeAction');
|
||||
},
|
||||
resetAction() {
|
||||
this.$emit('resetAction');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="filter" :class="actionInputStyles">
|
||||
<div class="filter-inputs">
|
||||
@@ -102,100 +196,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput.vue';
|
||||
import AutomationActionFileInput from './AutomationFileInput.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
export default {
|
||||
components: {
|
||||
AutomationActionTeamMessageInput,
|
||||
AutomationActionFileInput,
|
||||
WootMessageEditor,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
actionTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dropdownValues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showActionInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
initialFileName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isMacro: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
action_name: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_name;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_name: value });
|
||||
},
|
||||
},
|
||||
action_params: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_params;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_params: value });
|
||||
},
|
||||
},
|
||||
inputType() {
|
||||
return this.actionTypes.find(action => action.key === this.action_name)
|
||||
.inputType;
|
||||
},
|
||||
actionInputStyles() {
|
||||
return {
|
||||
'has-error': this.errorMessage,
|
||||
'is-a-macro': this.isMacro,
|
||||
};
|
||||
},
|
||||
castMessageVmodel: {
|
||||
get() {
|
||||
if (Array.isArray(this.action_params)) {
|
||||
return this.action_params[0];
|
||||
}
|
||||
return this.action_params;
|
||||
},
|
||||
set(value) {
|
||||
this.action_params = value;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeAction() {
|
||||
this.$emit('removeAction');
|
||||
},
|
||||
resetAction() {
|
||||
this.$emit('resetAction');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filter {
|
||||
@apply bg-slate-50 dark:bg-slate-800 p-2 border border-solid border-slate-75 dark:border-slate-600 rounded-md mb-2;
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedTeams"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_DROPDOWN_PLACEHOLDER')"
|
||||
:multiple="true"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="teams"
|
||||
:allow-empty="false"
|
||||
@input="updateValue"
|
||||
/>
|
||||
<textarea
|
||||
v-model="message"
|
||||
rows="4"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
@input="updateValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
// The value types are dynamic, hence prop validation removed to work with our action schema
|
||||
@@ -52,6 +25,33 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedTeams"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_DROPDOWN_PLACEHOLDER')"
|
||||
:multiple="true"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="teams"
|
||||
:allow-empty="false"
|
||||
@input="updateValue"
|
||||
/>
|
||||
<textarea
|
||||
v-model="message"
|
||||
rows="4"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
@input="updateValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
margin: var(--space-smaller) var(--space-zero);
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
<template>
|
||||
<label class="input-wrapper" :class="uploadState">
|
||||
<input
|
||||
v-if="uploadState !== 'processing'"
|
||||
type="file"
|
||||
name="attachment"
|
||||
:class="uploadState === 'processing' ? 'disabled' : ''"
|
||||
@change="onChangeFile"
|
||||
/>
|
||||
<spinner v-if="uploadState === 'processing'" />
|
||||
<fluent-icon v-if="uploadState === 'idle'" icon="file-upload" />
|
||||
<fluent-icon
|
||||
v-if="uploadState === 'uploaded'"
|
||||
icon="checkmark-circle"
|
||||
type="outline"
|
||||
class="success-icon"
|
||||
/>
|
||||
<fluent-icon
|
||||
v-if="uploadState === 'failed'"
|
||||
icon="dismiss-circle"
|
||||
type="outline"
|
||||
class="error-icon"
|
||||
/>
|
||||
<p class="file-button">{{ label }}</p>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
@@ -77,6 +50,33 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="input-wrapper" :class="uploadState">
|
||||
<input
|
||||
v-if="uploadState !== 'processing'"
|
||||
type="file"
|
||||
name="attachment"
|
||||
:class="uploadState === 'processing' ? 'disabled' : ''"
|
||||
@change="onChangeFile"
|
||||
/>
|
||||
<spinner v-if="uploadState === 'processing'" />
|
||||
<fluent-icon v-if="uploadState === 'idle'" icon="file-upload" />
|
||||
<fluent-icon
|
||||
v-if="uploadState === 'uploaded'"
|
||||
icon="checkmark-circle"
|
||||
type="outline"
|
||||
class="success-icon"
|
||||
/>
|
||||
<fluent-icon
|
||||
v-if="uploadState === 'failed'"
|
||||
icon="dismiss-circle"
|
||||
type="outline"
|
||||
class="error-icon"
|
||||
/>
|
||||
<p class="file-button">{{ label }}</p>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
input[type='file'] {
|
||||
@apply hidden;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<div class="avatar-container" :style="style" aria-hidden="true">
|
||||
<slot>{{ userInitial }}</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Avatar',
|
||||
@@ -38,6 +32,12 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="avatar-container" :style="style" aria-hidden="true">
|
||||
<slot>{{ userInitial }}</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
<template>
|
||||
<button
|
||||
class="header-section flex items-center text-base font-normal mr-4 ml-2 p-0 cursor-pointer text-woot-500 dark:text-woot-500"
|
||||
@click.capture="goBack"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" />
|
||||
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
import router from '../../routes/index';
|
||||
|
||||
@@ -32,3 +23,12 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="header-section flex items-center text-base font-normal mr-4 ml-2 p-0 cursor-pointer text-woot-500 dark:text-woot-500"
|
||||
@click.capture="goBack"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" />
|
||||
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
<template>
|
||||
<channel-selector
|
||||
:class="{ inactive: !isActive }"
|
||||
:title="channel.name"
|
||||
:src="getChannelThumbnail()"
|
||||
@click="onItemClick"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import ChannelSelector from '../ChannelSelector.vue';
|
||||
export default {
|
||||
@@ -65,3 +57,11 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<channel-selector
|
||||
:class="{ inactive: !isActive }"
|
||||
:title="channel.name"
|
||||
:src="getChannelThumbnail()"
|
||||
@click="onItemClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
<template>
|
||||
<woot-tabs :index="activeTabIndex" @change="onTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
<script>
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
@@ -51,3 +41,13 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<woot-tabs :index="activeTabIndex" @change="onTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
<template>
|
||||
<div class="colorpicker">
|
||||
<div
|
||||
class="colorpicker--selected"
|
||||
:style="`background-color: ${value}`"
|
||||
@click.prevent="toggleColorPicker"
|
||||
/>
|
||||
<chrome
|
||||
v-if="isPickerOpen"
|
||||
v-on-clickaway="closeTogglePicker"
|
||||
:disable-alpha="true"
|
||||
:value="value"
|
||||
class="colorpicker--chrome"
|
||||
@input="updateColor"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Chrome } from 'vue-color';
|
||||
|
||||
@@ -50,6 +32,24 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="colorpicker">
|
||||
<div
|
||||
class="colorpicker--selected"
|
||||
:style="`background-color: ${value}`"
|
||||
@click.prevent="toggleColorPicker"
|
||||
/>
|
||||
<chrome
|
||||
v-if="isPickerOpen"
|
||||
v-on-clickaway="closeTogglePicker"
|
||||
:disable-alpha="true"
|
||||
:value="value"
|
||||
class="colorpicker--chrome"
|
||||
@input="updateColor"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
<template>
|
||||
<div v-if="hasOpenedAtleastOnce" class="dashboard-app--container">
|
||||
<div
|
||||
v-for="(configItem, index) in config"
|
||||
:key="index"
|
||||
class="dashboard-app--list"
|
||||
>
|
||||
<loading-state
|
||||
v-if="iframeLoading"
|
||||
:message="$t('DASHBOARD_APPS.LOADING_MESSAGE')"
|
||||
class="dashboard-app_loading-container"
|
||||
/>
|
||||
<iframe
|
||||
v-if="configItem.type === 'frame' && configItem.url"
|
||||
:id="getFrameId(index)"
|
||||
:src="configItem.url"
|
||||
@load="() => onIframeLoad(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingState from 'dashboard/components/widgets/LoadingState.vue';
|
||||
export default {
|
||||
@@ -102,6 +80,28 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasOpenedAtleastOnce" class="dashboard-app--container">
|
||||
<div
|
||||
v-for="(configItem, index) in config"
|
||||
:key="index"
|
||||
class="dashboard-app--list"
|
||||
>
|
||||
<loading-state
|
||||
v-if="iframeLoading"
|
||||
:message="$t('DASHBOARD_APPS.LOADING_MESSAGE')"
|
||||
class="dashboard-app_loading-container"
|
||||
/>
|
||||
<iframe
|
||||
v-if="configItem.type === 'frame' && configItem.url"
|
||||
:id="getFrameId(index)"
|
||||
:src="configItem.url"
|
||||
@load="() => onIframeLoad(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-app--container,
|
||||
.dashboard-app--list,
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
message: { type: String, default: '' },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="empty-state py-16 px-1 ml-0 mr-0">
|
||||
<h3
|
||||
@@ -15,12 +24,3 @@
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
message: { type: String, default: '' },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
<template>
|
||||
<div v-if="isFeatureEnabled">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
@@ -23,3 +18,8 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="isFeatureEnabled">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,147 +1,3 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="rounded-md p-2 border border-solid"
|
||||
:class="getInputErrorClass(errorMessage)"
|
||||
>
|
||||
<div class="flex">
|
||||
<select
|
||||
v-if="groupedFilters"
|
||||
v-model="attributeKey"
|
||||
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<optgroup
|
||||
v-for="(group, i) in filterGroups"
|
||||
:key="i"
|
||||
:label="group.name"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in group.attributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select
|
||||
v-else
|
||||
v-model="attributeKey"
|
||||
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in filterAttributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
:disabled="attribute.disabled"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="filterOperator"
|
||||
class="bg-white dark:bg-slate-900 max-w-[20%] mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
>
|
||||
<option
|
||||
v-for="(operator, o) in operators"
|
||||
:key="o"
|
||||
:value="operator.value"
|
||||
>
|
||||
{{ $t(`FILTER.OPERATOR_LABELS.${operator.value}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div v-if="showUserInput" class="filter__answer--wrap mr-1 flex-grow">
|
||||
<div
|
||||
v-if="inputType === 'multi_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="'Select'"
|
||||
:multiple="true"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="inputType === 'search_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="'Select'"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
:option-height="104"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
|
||||
<input
|
||||
v-model="values"
|
||||
type="date"
|
||||
:editable="false"
|
||||
class="mb-0 datepicker"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
v-model="values"
|
||||
type="text"
|
||||
class="mb-0"
|
||||
placeholder="Enter value"
|
||||
/>
|
||||
</div>
|
||||
<woot-button
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="errorMessage" class="filter-error">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showQueryOperator"
|
||||
class="flex items-center justify-center relative my-2.5 mx-0"
|
||||
>
|
||||
<hr
|
||||
class="w-full absolute border-b border-solid border-slate-75 dark:border-slate-800"
|
||||
/>
|
||||
<select
|
||||
v-model="query_operator"
|
||||
class="bg-white dark:bg-slate-900 mb-0 w-auto relative text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
>
|
||||
<option value="and">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.AND') }}
|
||||
</option>
|
||||
<option value="or">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.OR') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -279,6 +135,150 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="p-2 border border-solid rounded-md"
|
||||
:class="getInputErrorClass(errorMessage)"
|
||||
>
|
||||
<div class="flex">
|
||||
<select
|
||||
v-if="groupedFilters"
|
||||
v-model="attributeKey"
|
||||
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<optgroup
|
||||
v-for="(group, i) in filterGroups"
|
||||
:key="i"
|
||||
:label="group.name"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in group.attributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select
|
||||
v-else
|
||||
v-model="attributeKey"
|
||||
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in filterAttributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
:disabled="attribute.disabled"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="filterOperator"
|
||||
class="bg-white dark:bg-slate-900 max-w-[20%] mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
>
|
||||
<option
|
||||
v-for="(operator, o) in operators"
|
||||
:key="o"
|
||||
:value="operator.value"
|
||||
>
|
||||
{{ $t(`FILTER.OPERATOR_LABELS.${operator.value}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div v-if="showUserInput" class="flex-grow mr-1 filter__answer--wrap">
|
||||
<div
|
||||
v-if="inputType === 'multi_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="'Select'"
|
||||
:multiple="true"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="inputType === 'search_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="'Select'"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
:option-height="104"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
|
||||
<input
|
||||
v-model="values"
|
||||
type="date"
|
||||
:editable="false"
|
||||
class="mb-0 datepicker"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
v-model="values"
|
||||
type="text"
|
||||
class="mb-0"
|
||||
placeholder="Enter value"
|
||||
/>
|
||||
</div>
|
||||
<woot-button
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="errorMessage" class="filter-error">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showQueryOperator"
|
||||
class="flex items-center justify-center relative my-2.5 mx-0"
|
||||
>
|
||||
<hr
|
||||
class="absolute w-full border-b border-solid border-slate-75 dark:border-slate-800"
|
||||
/>
|
||||
<select
|
||||
v-model="query_operator"
|
||||
class="relative w-auto mb-0 bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
|
||||
>
|
||||
<option value="and">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.AND') }}
|
||||
</option>
|
||||
<option value="or">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.OR') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.filter__answer--wrap {
|
||||
input {
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
<template>
|
||||
<div class="flex items-center h-[2.375rem] min-w-0 py-0 px-1">
|
||||
<span
|
||||
class="inline-flex rounded mr-1 rtl:ml-1 rtl:mr-0 bg-slate-25 dark:bg-slate-700 p-0.5 items-center flex-shrink-0 justify-center w-6 h-6"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="computedInboxIcon"
|
||||
size="14"
|
||||
class="text-slate-800 dark:text-slate-200"
|
||||
/>
|
||||
</span>
|
||||
<div class="flex flex-col w-full min-w-0 ml-1 mr-1">
|
||||
<h5 class="option__title">
|
||||
{{ name }}
|
||||
</h5>
|
||||
<p
|
||||
class="option__body overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
:title="inboxIdentifier"
|
||||
>
|
||||
{{ inboxIdentifier || computedInboxType }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getInboxClassByType,
|
||||
@@ -66,6 +41,31 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center h-[2.375rem] min-w-0 py-0 px-1">
|
||||
<span
|
||||
class="inline-flex rounded mr-1 rtl:ml-1 rtl:mr-0 bg-slate-25 dark:bg-slate-700 p-0.5 items-center flex-shrink-0 justify-center w-6 h-6"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="computedInboxIcon"
|
||||
size="14"
|
||||
class="text-slate-800 dark:text-slate-200"
|
||||
/>
|
||||
</span>
|
||||
<div class="flex flex-col w-full min-w-0 ml-1 mr-1">
|
||||
<h5 class="option__title">
|
||||
{{ name }}
|
||||
</h5>
|
||||
<p
|
||||
class="option__body overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
:title="inboxIdentifier"
|
||||
>
|
||||
{{ inboxIdentifier || computedInboxType }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.option__body {
|
||||
@apply inline-block text-slate-600 dark:text-slate-200 leading-[1.3] min-w-0 m-0;
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="inbox--name inline-flex items-center py-0.5 px-0 leading-3 whitespace-nowrap font-medium bg-none text-slate-600 dark:text-slate-500 text-xs my-0 mx-2.5"
|
||||
>
|
||||
<fluent-icon
|
||||
class="mr-0.5 rtl:ml-0.5 rtl:mr-0"
|
||||
:icon="computedInboxClass"
|
||||
size="12"
|
||||
/>
|
||||
{{ inbox.name }}
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||
|
||||
@@ -29,3 +17,15 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="inbox--name inline-flex items-center py-0.5 px-0 leading-3 whitespace-nowrap font-medium bg-none text-slate-600 dark:text-slate-500 text-xs my-0 mx-2.5"
|
||||
>
|
||||
<fluent-icon
|
||||
class="mr-0.5 rtl:ml-0.5 rtl:mr-0"
|
||||
:icon="computedInboxClass"
|
||||
size="12"
|
||||
/>
|
||||
{{ inbox.name }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,34 +1,3 @@
|
||||
<template>
|
||||
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
|
||||
<add-label @add="toggleLabels" />
|
||||
<woot-label
|
||||
v-for="label in savedLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:show-close="true"
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
@click="removeItem"
|
||||
/>
|
||||
<div class="dropdown-wrap">
|
||||
<div
|
||||
:class="{ 'dropdown-pane--open': showSearchDropdownLabel }"
|
||||
class="dropdown-pane"
|
||||
>
|
||||
<label-dropdown
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="allLabels"
|
||||
:selected-labels="selectedLabels"
|
||||
:allow-creation="isAdmin"
|
||||
@add="addItem"
|
||||
@remove="removeItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel.vue';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
@@ -107,6 +76,37 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
|
||||
<add-label @add="toggleLabels" />
|
||||
<woot-label
|
||||
v-for="label in savedLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:show-close="true"
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
@click="removeItem"
|
||||
/>
|
||||
<div class="dropdown-wrap">
|
||||
<div
|
||||
:class="{ 'dropdown-pane--open': showSearchDropdownLabel }"
|
||||
class="dropdown-pane"
|
||||
>
|
||||
<label-dropdown
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="allLabels"
|
||||
:selected-labels="selectedLabels"
|
||||
:allow-creation="isAdmin"
|
||||
@add="addItem"
|
||||
@remove="removeItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-icon {
|
||||
margin-right: var(--space-smaller);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: { type: String, default: '' },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex items-center justify-center p-8">
|
||||
<h6
|
||||
@@ -10,10 +17,3 @@
|
||||
</h6>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: { type: String, default: '' },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-slate-25 dark:bg-slate-900 pt-4 pb-0 px-8 border-b border-solid border-slate-50 dark:border-slate-800/50"
|
||||
@@ -14,18 +29,3 @@
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<span>
|
||||
{{ textToBeDisplayed }}
|
||||
<button
|
||||
v-if="text.length > limit"
|
||||
class="show-more--button"
|
||||
@click="toggleShowMore"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -47,6 +35,18 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span>
|
||||
{{ textToBeDisplayed }}
|
||||
<button
|
||||
v-if="text.length > limit"
|
||||
class="show-more--button"
|
||||
@click="toggleShowMore"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<style scoped>
|
||||
.show-more--button {
|
||||
color: var(--w-500);
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
<template>
|
||||
<footer
|
||||
v-if="isFooterVisible"
|
||||
class="h-12 flex items-center justify-between px-6"
|
||||
>
|
||||
<table-footer-results
|
||||
:first-index="firstIndex"
|
||||
:last-index="lastIndex"
|
||||
:total-count="totalCount"
|
||||
/>
|
||||
<table-footer-pagination
|
||||
v-if="totalCount"
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
:total-count="totalCount"
|
||||
:page-size="pageSize"
|
||||
@page-change="$emit('page-change', $event)"
|
||||
/>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import TableFooterResults from './TableFooterResults.vue';
|
||||
@@ -46,3 +25,24 @@ const isFooterVisible = computed(
|
||||
() => props.totalCount && !(firstIndex.value > props.totalCount)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer
|
||||
v-if="isFooterVisible"
|
||||
class="h-12 flex items-center justify-between px-6"
|
||||
>
|
||||
<table-footer-results
|
||||
:first-index="firstIndex"
|
||||
:last-index="lastIndex"
|
||||
:total-count="totalCount"
|
||||
/>
|
||||
<table-footer-pagination
|
||||
v-if="totalCount"
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
:total-count="totalCount"
|
||||
:page-size="pageSize"
|
||||
@page-change="$emit('page-change', $event)"
|
||||
/>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,64 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const hasLastPage = computed(
|
||||
() => props.currentPage === props.totalPages || props.totalPages === 1
|
||||
);
|
||||
const hasFirstPage = computed(() => props.currentPage === 1);
|
||||
const hasNextPage = computed(() => props.currentPage === props.totalPages);
|
||||
const hasPrevPage = computed(() => props.currentPage === 1);
|
||||
|
||||
const emit = defineEmits(['page-change']);
|
||||
|
||||
function buttonClass(hasPage) {
|
||||
if (hasPage) {
|
||||
return 'hover:!bg-slate-50 dark:hover:!bg-slate-800';
|
||||
}
|
||||
return 'dark:hover:!bg-slate-700/50';
|
||||
}
|
||||
|
||||
function onPageChange(newPage) {
|
||||
emit('page-change', newPage);
|
||||
}
|
||||
|
||||
const onNextPage = () => {
|
||||
if (!onNextPage.value) {
|
||||
onPageChange(props.currentPage + 1);
|
||||
}
|
||||
};
|
||||
const onPrevPage = () => {
|
||||
if (!hasPrevPage.value) {
|
||||
onPageChange(props.currentPage - 1);
|
||||
}
|
||||
};
|
||||
const onFirstPage = () => {
|
||||
if (!hasFirstPage.value) {
|
||||
onPageChange(1);
|
||||
}
|
||||
};
|
||||
const onLastPage = () => {
|
||||
if (!hasLastPage.value) {
|
||||
onPageChange(props.totalPages);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center bg-slate-50 dark:bg-slate-800 h-8 rounded-lg">
|
||||
<woot-button
|
||||
@@ -80,64 +141,3 @@
|
||||
</woot-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const hasLastPage = computed(
|
||||
() => props.currentPage === props.totalPages || props.totalPages === 1
|
||||
);
|
||||
const hasFirstPage = computed(() => props.currentPage === 1);
|
||||
const hasNextPage = computed(() => props.currentPage === props.totalPages);
|
||||
const hasPrevPage = computed(() => props.currentPage === 1);
|
||||
|
||||
const emit = defineEmits(['page-change']);
|
||||
|
||||
function buttonClass(hasPage) {
|
||||
if (hasPage) {
|
||||
return 'hover:!bg-slate-50 dark:hover:!bg-slate-800';
|
||||
}
|
||||
return 'dark:hover:!bg-slate-700/50';
|
||||
}
|
||||
|
||||
function onPageChange(newPage) {
|
||||
emit('page-change', newPage);
|
||||
}
|
||||
|
||||
const onNextPage = () => {
|
||||
if (!onNextPage.value) {
|
||||
onPageChange(props.currentPage + 1);
|
||||
}
|
||||
};
|
||||
const onPrevPage = () => {
|
||||
if (!hasPrevPage.value) {
|
||||
onPageChange(props.currentPage - 1);
|
||||
}
|
||||
};
|
||||
const onFirstPage = () => {
|
||||
if (!hasFirstPage.value) {
|
||||
onPageChange(1);
|
||||
}
|
||||
};
|
||||
const onLastPage = () => {
|
||||
if (!hasLastPage.value) {
|
||||
onPageChange(props.totalPages);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 font-medium">
|
||||
{{
|
||||
$t('GENERAL.SHOWING_RESULTS', {
|
||||
firstIndex,
|
||||
lastIndex,
|
||||
totalCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
firstIndex: {
|
||||
@@ -26,3 +14,15 @@ defineProps({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 font-medium">
|
||||
{{
|
||||
$t('GENERAL.SHOWING_RESULTS', {
|
||||
firstIndex,
|
||||
lastIndex,
|
||||
totalCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,40 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="thumbnailBoxClass"
|
||||
:style="{ height: size, width: size }"
|
||||
:title="title"
|
||||
>
|
||||
<!-- Using v-show instead of v-if to avoid flickering as v-if removes dom elements. -->
|
||||
<slot>
|
||||
<img
|
||||
v-show="shouldShowImage"
|
||||
:src="src"
|
||||
draggable="false"
|
||||
:class="thumbnailClass"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
/>
|
||||
<Avatar
|
||||
v-show="!shouldShowImage"
|
||||
:username="userNameWithoutEmoji"
|
||||
:class="thumbnailClass"
|
||||
:size="avatarSize"
|
||||
/>
|
||||
</slot>
|
||||
<img
|
||||
v-if="badgeSrc"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
:src="`/integrations/channels/badges/${badgeSrc}.png`"
|
||||
alt="Badge"
|
||||
/>
|
||||
<div
|
||||
v-if="showStatusIndicator"
|
||||
:class="`source-badge user-online-status user-online-status--${status}`"
|
||||
:style="statusStyle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Thumbnail Component
|
||||
@@ -167,6 +130,43 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="thumbnailBoxClass"
|
||||
:style="{ height: size, width: size }"
|
||||
:title="title"
|
||||
>
|
||||
<!-- Using v-show instead of v-if to avoid flickering as v-if removes dom elements. -->
|
||||
<slot>
|
||||
<img
|
||||
v-show="shouldShowImage"
|
||||
:src="src"
|
||||
draggable="false"
|
||||
:class="thumbnailClass"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
/>
|
||||
<Avatar
|
||||
v-show="!shouldShowImage"
|
||||
:username="userNameWithoutEmoji"
|
||||
:class="thumbnailClass"
|
||||
:size="avatarSize"
|
||||
/>
|
||||
</slot>
|
||||
<img
|
||||
v-if="badgeSrc"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
:src="`/integrations/channels/badges/${badgeSrc}.png`"
|
||||
alt="Badge"
|
||||
/>
|
||||
<div
|
||||
v-if="showStatusIndicator"
|
||||
:class="`source-badge user-online-status user-online-status--${status}`"
|
||||
:style="statusStyle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-thumbnail-box {
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
<template>
|
||||
<div class="overlapping-thumbnails">
|
||||
<thumbnail
|
||||
v-for="user in usersList"
|
||||
:key="user.id"
|
||||
v-tooltip="user.name"
|
||||
:title="user.name"
|
||||
:src="user.thumbnail"
|
||||
:username="user.name"
|
||||
:has-border="true"
|
||||
:size="size"
|
||||
:class="`overlapping-thumbnail gap-${gap}`"
|
||||
/>
|
||||
<span v-if="showMoreThumbnailsCount" class="thumbnail-more-text">
|
||||
{{ moreThumbnailsText }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Thumbnail from './Thumbnail.vue';
|
||||
|
||||
@@ -51,6 +33,24 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="overlapping-thumbnails">
|
||||
<thumbnail
|
||||
v-for="user in usersList"
|
||||
:key="user.id"
|
||||
v-tooltip="user.name"
|
||||
:title="user.name"
|
||||
:src="user.thumbnail"
|
||||
:username="user.name"
|
||||
:has-border="true"
|
||||
:size="size"
|
||||
:class="`overlapping-thumbnail gap-${gap}`"
|
||||
/>
|
||||
<span v-if="showMoreThumbnailsCount" class="thumbnail-more-text">
|
||||
{{ moreThumbnailsText }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.overlapping-thumbnails {
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-1.5 text-left">
|
||||
<thumbnail
|
||||
:src="user.thumbnail"
|
||||
:size="size"
|
||||
:username="user.name"
|
||||
:status="user.availability_status"
|
||||
/>
|
||||
<h6
|
||||
class="my-0 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
:class="textClass"
|
||||
>
|
||||
{{ user.name }}
|
||||
</h6>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
|
||||
@@ -37,3 +21,19 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex items-center gap-1.5 text-left">
|
||||
<thumbnail
|
||||
:src="user.thumbnail"
|
||||
:size="size"
|
||||
:username="user.name"
|
||||
:status="user.availability_status"
|
||||
/>
|
||||
<h6
|
||||
class="my-0 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
:class="textClass"
|
||||
>
|
||||
{{ user.name }}
|
||||
</h6>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
<template>
|
||||
<woot-button
|
||||
v-if="isVideoIntegrationEnabled"
|
||||
v-tooltip.top-end="
|
||||
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||
"
|
||||
icon="video"
|
||||
:is-loading="isLoading"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||
@@ -54,3 +40,17 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<woot-button
|
||||
v-if="isVideoIntegrationEnabled"
|
||||
v-tooltip.top-end="
|
||||
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||
"
|
||||
icon="video"
|
||||
:is-loading="isLoading"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<div class="audio-wave-wrapper">
|
||||
<audio id="audio-wave" class="video-js vjs-fill vjs-default-skin" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import getUuid from 'widget/helpers/uuid';
|
||||
import 'video.js/dist/video-js.css';
|
||||
@@ -240,6 +234,12 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="audio-wave-wrapper">
|
||||
<audio id="audio-wave" class="video-js vjs-fill vjs-default-skin" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.audio-wave-wrapper {
|
||||
@apply h-20 min-h-[5rem];
|
||||
|
||||
@@ -1,49 +1,3 @@
|
||||
<template>
|
||||
<div ref="editorRoot" class="relative editor-root">
|
||||
<tag-agents
|
||||
v-if="showUserMentions && isPrivate"
|
||||
:search-key="mentionSearchKey"
|
||||
@click="insertMentionNode"
|
||||
/>
|
||||
<canned-response
|
||||
v-if="shouldShowCannedResponses"
|
||||
:search-key="cannedSearchTerm"
|
||||
@click="insertCannedResponse"
|
||||
/>
|
||||
<variable-list
|
||||
v-if="shouldShowVariables"
|
||||
:search-key="variableSearchTerm"
|
||||
@click="insertVariable"
|
||||
/>
|
||||
<input
|
||||
ref="imageUpload"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
<div
|
||||
v-show="isImageNodeSelected && showImageResizeToolbar"
|
||||
class="absolute shadow-md rounded-[4px] flex gap-1 py-1 px-1 bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-50"
|
||||
:style="{
|
||||
top: toolbarPosition.top,
|
||||
left: toolbarPosition.left,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-for="size in sizes"
|
||||
:key="size.name"
|
||||
class="text-xs font-medium rounded-[4px] border border-solid border-slate-200 dark:border-slate-600 px-1.5 py-0.5 hover:bg-slate-100 dark:hover:bg-slate-800"
|
||||
@click="setURLWithQueryAndImageSize(size)"
|
||||
>
|
||||
{{ size.name }}
|
||||
</button>
|
||||
</div>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
messageSchema,
|
||||
@@ -728,6 +682,52 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="editorRoot" class="relative editor-root">
|
||||
<tag-agents
|
||||
v-if="showUserMentions && isPrivate"
|
||||
:search-key="mentionSearchKey"
|
||||
@click="insertMentionNode"
|
||||
/>
|
||||
<canned-response
|
||||
v-if="shouldShowCannedResponses"
|
||||
:search-key="cannedSearchTerm"
|
||||
@click="insertCannedResponse"
|
||||
/>
|
||||
<variable-list
|
||||
v-if="shouldShowVariables"
|
||||
:search-key="variableSearchTerm"
|
||||
@click="insertVariable"
|
||||
/>
|
||||
<input
|
||||
ref="imageUpload"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
<div
|
||||
v-show="isImageNodeSelected && showImageResizeToolbar"
|
||||
class="absolute shadow-md rounded-[4px] flex gap-1 py-1 px-1 bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-50"
|
||||
:style="{
|
||||
top: toolbarPosition.top,
|
||||
left: toolbarPosition.left,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-for="size in sizes"
|
||||
:key="size.name"
|
||||
class="text-xs font-medium rounded-[4px] border border-solid border-slate-200 dark:border-slate-600 px-1.5 py-0.5 hover:bg-slate-100 dark:hover:bg-slate-800"
|
||||
@click="setURLWithQueryAndImageSize(size)"
|
||||
>
|
||||
{{ size.name }}
|
||||
</button>
|
||||
</div>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss';
|
||||
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="editor-root editor--article">
|
||||
<input
|
||||
ref="imageUploadInput"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
fullSchema,
|
||||
@@ -223,6 +208,21 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="editor-root editor--article">
|
||||
<input
|
||||
ref="imageUploadInput"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@chatwoot/prosemirror-schema/src/styles/article.scss';
|
||||
|
||||
|
||||
@@ -1,138 +1,3 @@
|
||||
<template>
|
||||
<div class="bottom-box" :class="wrapClass">
|
||||
<div class="left-wrap">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||
icon="emoji"
|
||||
emoji="😊"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleEmojiPicker"
|
||||
/>
|
||||
<file-upload
|
||||
ref="upload"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||
input-id="conversationAttachment"
|
||||
:size="4096 * 4096"
|
||||
:accept="allowedFileTypes"
|
||||
:multiple="enableMultipleFileUpload"
|
||||
:drop="enableDragAndDrop"
|
||||
:drop-directory="false"
|
||||
:data="{
|
||||
direct_upload_url: '/rails/active_storage/direct_uploads',
|
||||
direct_upload: true,
|
||||
}"
|
||||
@input-file="onFileUpload"
|
||||
>
|
||||
<woot-button
|
||||
v-if="showAttachButton"
|
||||
class-names="button--upload"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||
icon="attach"
|
||||
emoji="📎"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
/>
|
||||
</file-upload>
|
||||
<woot-button
|
||||
v-if="showAudioRecorderButton"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
||||
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
|
||||
emoji="🎤"
|
||||
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleAudioRecorder"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showEditorToggle"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
icon="quote"
|
||||
emoji="🖊️"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="$emit('toggle-editor')"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showAudioPlayStopButton"
|
||||
:icon="audioRecorderPlayStopIcon"
|
||||
emoji="🎤"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleAudioRecorderPlayPause"
|
||||
>
|
||||
<span>{{ recordingAudioDurationText }}</span>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showMessageSignatureButton"
|
||||
v-tooltip.top-end="signatureToggleTooltip"
|
||||
icon="signature"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="signatureToggleTooltip"
|
||||
@click="toggleMessageSignature"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="hasWhatsappTemplates"
|
||||
v-tooltip.top-end="'Whatsapp Templates'"
|
||||
icon="whatsapp"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="'Whatsapp Templates'"
|
||||
@click="$emit('selectWhatsappTemplate')"
|
||||
/>
|
||||
<video-call-button
|
||||
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
<AIAssistanceButton
|
||||
:conversation-id="conversationId"
|
||||
:is-private-note="isOnPrivateNote"
|
||||
:message="message"
|
||||
@replace-text="replaceText"
|
||||
/>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-show="$refs.upload && $refs.upload.dropActive"
|
||||
class="fixed top-0 bottom-0 left-0 right-0 z-20 flex flex-col items-center justify-center w-full h-full gap-2 text-slate-900 dark:text-slate-50 bg-modal-backdrop-light dark:bg-modal-backdrop-dark"
|
||||
>
|
||||
<fluent-icon icon="cloud-backup" size="40" />
|
||||
<h4 class="text-2xl break-words text-slate-900 dark:text-slate-50">
|
||||
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
|
||||
</h4>
|
||||
</div>
|
||||
</transition>
|
||||
<woot-button
|
||||
v-if="enableInsertArticleInReply"
|
||||
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
icon="document-text-link"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
@click="toggleInsertArticle"
|
||||
/>
|
||||
</div>
|
||||
<div class="right-wrap">
|
||||
<woot-button
|
||||
size="small"
|
||||
:class-names="buttonClass"
|
||||
:is-disabled="isSendDisabled"
|
||||
@click="onSend"
|
||||
>
|
||||
{{ sendButtonText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import FileUpload from 'vue-upload-component';
|
||||
@@ -371,6 +236,141 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bottom-box" :class="wrapClass">
|
||||
<div class="left-wrap">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||
icon="emoji"
|
||||
emoji="😊"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleEmojiPicker"
|
||||
/>
|
||||
<file-upload
|
||||
ref="upload"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||
input-id="conversationAttachment"
|
||||
:size="4096 * 4096"
|
||||
:accept="allowedFileTypes"
|
||||
:multiple="enableMultipleFileUpload"
|
||||
:drop="enableDragAndDrop"
|
||||
:drop-directory="false"
|
||||
:data="{
|
||||
direct_upload_url: '/rails/active_storage/direct_uploads',
|
||||
direct_upload: true,
|
||||
}"
|
||||
@input-file="onFileUpload"
|
||||
>
|
||||
<woot-button
|
||||
v-if="showAttachButton"
|
||||
class-names="button--upload"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||
icon="attach"
|
||||
emoji="📎"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
/>
|
||||
</file-upload>
|
||||
<woot-button
|
||||
v-if="showAudioRecorderButton"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
||||
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
|
||||
emoji="🎤"
|
||||
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleAudioRecorder"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showEditorToggle"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
icon="quote"
|
||||
emoji="🖊️"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="$emit('toggle-editor')"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showAudioPlayStopButton"
|
||||
:icon="audioRecorderPlayStopIcon"
|
||||
emoji="🎤"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleAudioRecorderPlayPause"
|
||||
>
|
||||
<span>{{ recordingAudioDurationText }}</span>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showMessageSignatureButton"
|
||||
v-tooltip.top-end="signatureToggleTooltip"
|
||||
icon="signature"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="signatureToggleTooltip"
|
||||
@click="toggleMessageSignature"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="hasWhatsappTemplates"
|
||||
v-tooltip.top-end="'Whatsapp Templates'"
|
||||
icon="whatsapp"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="'Whatsapp Templates'"
|
||||
@click="$emit('selectWhatsappTemplate')"
|
||||
/>
|
||||
<video-call-button
|
||||
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
<AIAssistanceButton
|
||||
:conversation-id="conversationId"
|
||||
:is-private-note="isOnPrivateNote"
|
||||
:message="message"
|
||||
@replace-text="replaceText"
|
||||
/>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-show="$refs.upload && $refs.upload.dropActive"
|
||||
class="fixed top-0 bottom-0 left-0 right-0 z-20 flex flex-col items-center justify-center w-full h-full gap-2 text-slate-900 dark:text-slate-50 bg-modal-backdrop-light dark:bg-modal-backdrop-dark"
|
||||
>
|
||||
<fluent-icon icon="cloud-backup" size="40" />
|
||||
<h4 class="text-2xl break-words text-slate-900 dark:text-slate-50">
|
||||
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
|
||||
</h4>
|
||||
</div>
|
||||
</transition>
|
||||
<woot-button
|
||||
v-if="enableInsertArticleInReply"
|
||||
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
icon="document-text-link"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
@click="toggleInsertArticle"
|
||||
/>
|
||||
</div>
|
||||
<div class="right-wrap">
|
||||
<woot-button
|
||||
size="small"
|
||||
:class-names="buttonClass"
|
||||
:is-disabled="isSendDisabled"
|
||||
@click="onSend"
|
||||
>
|
||||
{{ sendButtonText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bottom-box {
|
||||
@apply flex justify-between py-3 px-4;
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
<template>
|
||||
<div class="flex justify-between bg-black-50 dark:bg-slate-800">
|
||||
<div class="button-group">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
class="button--reply"
|
||||
:class="replyButtonClass"
|
||||
@click="handleReplyClick"
|
||||
>
|
||||
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
||||
</woot-button>
|
||||
|
||||
<woot-button
|
||||
class="button--note"
|
||||
variant="clear"
|
||||
color-scheme="warning"
|
||||
:class="noteButtonClass"
|
||||
@click="handleNoteClick"
|
||||
>
|
||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div class="flex items-center mx-4 my-0">
|
||||
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
|
||||
<span :class="charLengthClass">
|
||||
{{ characterLengthWarning }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="popoutReplyBox"
|
||||
variant="clear"
|
||||
icon="dismiss"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
<woot-button
|
||||
v-else
|
||||
variant="clear"
|
||||
icon="resize-large"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
@@ -117,6 +69,54 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between bg-black-50 dark:bg-slate-800">
|
||||
<div class="button-group">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
class="button--reply"
|
||||
:class="replyButtonClass"
|
||||
@click="handleReplyClick"
|
||||
>
|
||||
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
||||
</woot-button>
|
||||
|
||||
<woot-button
|
||||
class="button--note"
|
||||
variant="clear"
|
||||
color-scheme="warning"
|
||||
:class="noteButtonClass"
|
||||
@click="handleNoteClick"
|
||||
>
|
||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div class="flex items-center mx-4 my-0">
|
||||
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
|
||||
<span :class="charLengthClass">
|
||||
{{ characterLengthWarning }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="popoutReplyBox"
|
||||
variant="clear"
|
||||
icon="dismiss"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
<woot-button
|
||||
v-else
|
||||
variant="clear"
|
||||
icon="resize-large"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button-group {
|
||||
@apply flex border-0 p-0 m-0;
|
||||
|
||||
+5
-5
@@ -1,8 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
:class="`status-badge status-badge__${status} rounded-full w-2.5 h-2.5 mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-flex`"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -10,6 +5,11 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="`status-badge status-badge__${status} rounded-full w-2.5 h-2.5 mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-flex`"
|
||||
/>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.status-badge {
|
||||
&__online {
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<mention-box
|
||||
v-if="items.length"
|
||||
:items="items"
|
||||
@mention-select="handleMentionClick"
|
||||
>
|
||||
<template slot-scope="{ item }">
|
||||
<strong>{{ item.label }}</strong> - {{ item.description }}
|
||||
</template>
|
||||
</mention-box>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import MentionBox from '../mentions/MentionBox.vue';
|
||||
@@ -52,3 +40,15 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<mention-box
|
||||
v-if="items.length"
|
||||
:items="items"
|
||||
@mention-select="handleMentionClick"
|
||||
>
|
||||
<template slot-scope="{ item }">
|
||||
<strong>{{ item.label }}</strong> - {{ item.description }}
|
||||
</template>
|
||||
</mention-box>
|
||||
</template>
|
||||
|
||||
+80
-80
@@ -1,83 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<woot-modal-header :header-title="filterModalHeaderTitle">
|
||||
<p class="text-slate-600 dark:text-slate-200">
|
||||
{{ filterModalSubTitle }}
|
||||
</p>
|
||||
</woot-modal-header>
|
||||
<div class="p-8">
|
||||
<div v-if="isFolderView">
|
||||
<label class="input-label" :class="{ error: !activeFolderNewName }">
|
||||
{{ $t('FILTER.FOLDER_LABEL') }}
|
||||
<input
|
||||
v-model="activeFolderNewName"
|
||||
type="text"
|
||||
class="bg-white folder-input border-slate-75 dark:border-slate-600 dark:bg-slate-900 text-slate-900 dark:text-slate-100"
|
||||
/>
|
||||
<span v-if="!activeFolderNewName" class="message">
|
||||
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="mb-1">
|
||||
{{ $t('FILTER.FOLDER_QUERY_LABEL') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 mb-4 border border-solid rounded-lg bg-slate-25 dark:bg-slate-900 border-slate-50 dark:border-slate-700/50"
|
||||
>
|
||||
<filter-input-box
|
||||
v-for="(filter, i) in appliedFilters"
|
||||
:key="i"
|
||||
v-model="appliedFilters[i]"
|
||||
:filter-groups="filterGroups"
|
||||
:input-type="
|
||||
getInputType(
|
||||
appliedFilters[i].attribute_key,
|
||||
appliedFilters[i].filter_operator
|
||||
)
|
||||
"
|
||||
:operators="getOperators(appliedFilters[i].attribute_key)"
|
||||
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
|
||||
:show-query-operator="i !== appliedFilters.length - 1"
|
||||
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
|
||||
:grouped-filters="true"
|
||||
:error-message="validationErrors[`filter_${i}`]"
|
||||
@resetFilter="resetFilter(i, appliedFilters[i])"
|
||||
@removeFilter="removeFilter(i)"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<woot-button
|
||||
icon="add"
|
||||
color-scheme="success"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="appendNewFilter"
|
||||
>
|
||||
{{ $t('FILTER.ADD_NEW_FILTER') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||
<woot-button class="button clear" @click.prevent="onClose">
|
||||
{{ $t('FILTER.CANCEL_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="isFolderView"
|
||||
:disabled="!activeFolderNewName"
|
||||
@click="updateSavedCustomViews"
|
||||
>
|
||||
{{ $t('FILTER.UPDATE_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button v-else @click="submitFilterQuery">
|
||||
{{ $t('FILTER.SUBMIT_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import FilterInputBox from '../FilterInput/Index.vue';
|
||||
@@ -362,6 +282,86 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<woot-modal-header :header-title="filterModalHeaderTitle">
|
||||
<p class="text-slate-600 dark:text-slate-200">
|
||||
{{ filterModalSubTitle }}
|
||||
</p>
|
||||
</woot-modal-header>
|
||||
<div class="p-8">
|
||||
<div v-if="isFolderView">
|
||||
<label class="input-label" :class="{ error: !activeFolderNewName }">
|
||||
{{ $t('FILTER.FOLDER_LABEL') }}
|
||||
<input
|
||||
v-model="activeFolderNewName"
|
||||
type="text"
|
||||
class="bg-white folder-input border-slate-75 dark:border-slate-600 dark:bg-slate-900 text-slate-900 dark:text-slate-100"
|
||||
/>
|
||||
<span v-if="!activeFolderNewName" class="message">
|
||||
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="mb-1">
|
||||
{{ $t('FILTER.FOLDER_QUERY_LABEL') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 mb-4 border border-solid rounded-lg bg-slate-25 dark:bg-slate-900 border-slate-50 dark:border-slate-700/50"
|
||||
>
|
||||
<filter-input-box
|
||||
v-for="(filter, i) in appliedFilters"
|
||||
:key="i"
|
||||
v-model="appliedFilters[i]"
|
||||
:filter-groups="filterGroups"
|
||||
:input-type="
|
||||
getInputType(
|
||||
appliedFilters[i].attribute_key,
|
||||
appliedFilters[i].filter_operator
|
||||
)
|
||||
"
|
||||
:operators="getOperators(appliedFilters[i].attribute_key)"
|
||||
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
|
||||
:show-query-operator="i !== appliedFilters.length - 1"
|
||||
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
|
||||
:grouped-filters="true"
|
||||
:error-message="validationErrors[`filter_${i}`]"
|
||||
@resetFilter="resetFilter(i, appliedFilters[i])"
|
||||
@removeFilter="removeFilter(i)"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<woot-button
|
||||
icon="add"
|
||||
color-scheme="success"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="appendNewFilter"
|
||||
>
|
||||
{{ $t('FILTER.ADD_NEW_FILTER') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||
<woot-button class="button clear" @click.prevent="onClose">
|
||||
{{ $t('FILTER.CANCEL_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="isFolderView"
|
||||
:disabled="!activeFolderNewName"
|
||||
@click="updateSavedCustomViews"
|
||||
>
|
||||
{{ $t('FILTER.UPDATE_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button v-else @click="submitFilterQuery">
|
||||
{{ $t('FILTER.SUBMIT_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.folder-input {
|
||||
@apply w-[50%];
|
||||
|
||||
+44
-44
@@ -1,47 +1,3 @@
|
||||
<template>
|
||||
<div class="relative flex">
|
||||
<woot-button
|
||||
v-tooltip.right="$t('CHAT_LIST.SORT_TOOLTIP_LABEL')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class="selector-button"
|
||||
icon="sort-icon"
|
||||
@click="toggleDropdown"
|
||||
/>
|
||||
<div
|
||||
v-if="showActionsDropdown"
|
||||
v-on-clickaway="closeDropdown"
|
||||
class="right-0 mt-1 dropdown-pane dropdown-pane--open basic-filter"
|
||||
>
|
||||
<div class="flex items-center justify-between last:mt-4">
|
||||
<span class="text-xs font-medium text-slate-800 dark:text-slate-100">{{
|
||||
$t('CHAT_LIST.CHAT_SORT.STATUS')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="status"
|
||||
:selected-value="chatStatus"
|
||||
:items="chatStatusItems"
|
||||
path-prefix="CHAT_LIST.CHAT_STATUS_FILTER_ITEMS"
|
||||
@onChangeFilter="onChangeFilter"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between last:mt-4">
|
||||
<span class="text-xs font-medium text-slate-800 dark:text-slate-100">{{
|
||||
$t('CHAT_LIST.CHAT_SORT.ORDER_BY')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="sort"
|
||||
:selected-value="sortFilter"
|
||||
:items="chatSortItems"
|
||||
path-prefix="CHAT_LIST.SORT_ORDER_ITEMS"
|
||||
@onChangeFilter="onChangeFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -106,6 +62,50 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative flex">
|
||||
<woot-button
|
||||
v-tooltip.right="$t('CHAT_LIST.SORT_TOOLTIP_LABEL')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class="selector-button"
|
||||
icon="sort-icon"
|
||||
@click="toggleDropdown"
|
||||
/>
|
||||
<div
|
||||
v-if="showActionsDropdown"
|
||||
v-on-clickaway="closeDropdown"
|
||||
class="right-0 mt-1 dropdown-pane dropdown-pane--open basic-filter"
|
||||
>
|
||||
<div class="flex items-center justify-between last:mt-4">
|
||||
<span class="text-xs font-medium text-slate-800 dark:text-slate-100">{{
|
||||
$t('CHAT_LIST.CHAT_SORT.STATUS')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="status"
|
||||
:selected-value="chatStatus"
|
||||
:items="chatStatusItems"
|
||||
path-prefix="CHAT_LIST.CHAT_STATUS_FILTER_ITEMS"
|
||||
@onChangeFilter="onChangeFilter"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between last:mt-4">
|
||||
<span class="text-xs font-medium text-slate-800 dark:text-slate-100">{{
|
||||
$t('CHAT_LIST.CHAT_SORT.ORDER_BY')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="sort"
|
||||
:selected-value="sortFilter"
|
||||
:items="chatSortItems"
|
||||
path-prefix="CHAT_LIST.SORT_ORDER_ITEMS"
|
||||
@onChangeFilter="onChangeFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.basic-filter {
|
||||
@apply w-52 p-4 top-6;
|
||||
|
||||
@@ -1,67 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="conversation-details-wrap bg-slate-25 dark:bg-slate-800"
|
||||
:class="{ 'with-border-right': !isOnExpandedLayout }"
|
||||
>
|
||||
<conversation-header
|
||||
v-if="currentChat.id"
|
||||
:chat="currentChat"
|
||||
:is-inbox-view="isInboxView"
|
||||
:is-contact-panel-open="isContactPanelOpen"
|
||||
:show-back-button="isOnExpandedLayout && !isInboxView"
|
||||
@contact-panel-toggle="onToggleContactPanel"
|
||||
/>
|
||||
<woot-tabs
|
||||
v-if="dashboardApps.length && currentChat.id"
|
||||
:index="activeIndex"
|
||||
class="dashboard-app--tabs bg-white dark:bg-slate-900 -mt-px"
|
||||
@change="onDashboardAppTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
v-for="tab in dashboardAppTabs"
|
||||
:key="tab.key"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
</woot-tabs>
|
||||
<div
|
||||
v-show="!activeIndex"
|
||||
class="flex bg-slate-25 dark:bg-slate-800 m-0 h-full min-h-0"
|
||||
>
|
||||
<messages-view
|
||||
v-if="currentChat.id"
|
||||
:inbox-id="inboxId"
|
||||
:is-inbox-view="isInboxView"
|
||||
:is-contact-panel-open="isContactPanelOpen"
|
||||
@contact-panel-toggle="onToggleContactPanel"
|
||||
/>
|
||||
<empty-state
|
||||
v-if="!currentChat.id && !isInboxView"
|
||||
:is-on-expanded-layout="isOnExpandedLayout"
|
||||
/>
|
||||
<div
|
||||
v-show="showContactPanel"
|
||||
class="conversation-sidebar-wrap basis-full sm:basis-[17.5rem] md:basis-[18.75rem] lg:basis-[19.375rem] xl:basis-[20.625rem] 2xl:basis-[25rem] rtl:border-r border-slate-50 dark:border-slate-700 h-auto overflow-auto z-10 flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<contact-panel
|
||||
v-if="showContactPanel"
|
||||
:conversation-id="currentChat.id"
|
||||
:inbox-id="currentChat.inbox_id"
|
||||
:on-toggle="onToggleContactPanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<dashboard-app-frame
|
||||
v-for="(dashboardApp, index) in dashboardApps"
|
||||
v-show="activeIndex - 1 === index"
|
||||
:key="currentChat.id + '-' + dashboardApp.id"
|
||||
:is-visible="activeIndex - 1 === index"
|
||||
:config="dashboardApps[index].content"
|
||||
:position="index"
|
||||
:current-chat="currentChat"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ContactPanel from 'dashboard/routes/dashboard/conversation/ContactPanel.vue';
|
||||
@@ -153,6 +89,70 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="conversation-details-wrap bg-slate-25 dark:bg-slate-800"
|
||||
:class="{ 'with-border-right': !isOnExpandedLayout }"
|
||||
>
|
||||
<conversation-header
|
||||
v-if="currentChat.id"
|
||||
:chat="currentChat"
|
||||
:is-inbox-view="isInboxView"
|
||||
:is-contact-panel-open="isContactPanelOpen"
|
||||
:show-back-button="isOnExpandedLayout && !isInboxView"
|
||||
@contact-panel-toggle="onToggleContactPanel"
|
||||
/>
|
||||
<woot-tabs
|
||||
v-if="dashboardApps.length && currentChat.id"
|
||||
:index="activeIndex"
|
||||
class="dashboard-app--tabs bg-white dark:bg-slate-900 -mt-px"
|
||||
@change="onDashboardAppTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
v-for="tab in dashboardAppTabs"
|
||||
:key="tab.key"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
</woot-tabs>
|
||||
<div
|
||||
v-show="!activeIndex"
|
||||
class="flex bg-slate-25 dark:bg-slate-800 m-0 h-full min-h-0"
|
||||
>
|
||||
<messages-view
|
||||
v-if="currentChat.id"
|
||||
:inbox-id="inboxId"
|
||||
:is-inbox-view="isInboxView"
|
||||
:is-contact-panel-open="isContactPanelOpen"
|
||||
@contact-panel-toggle="onToggleContactPanel"
|
||||
/>
|
||||
<empty-state
|
||||
v-if="!currentChat.id && !isInboxView"
|
||||
:is-on-expanded-layout="isOnExpandedLayout"
|
||||
/>
|
||||
<div
|
||||
v-show="showContactPanel"
|
||||
class="conversation-sidebar-wrap basis-full sm:basis-[17.5rem] md:basis-[18.75rem] lg:basis-[19.375rem] xl:basis-[20.625rem] 2xl:basis-[25rem] rtl:border-r border-slate-50 dark:border-slate-700 h-auto overflow-auto z-10 flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<contact-panel
|
||||
v-if="showContactPanel"
|
||||
:conversation-id="currentChat.id"
|
||||
:inbox-id="currentChat.inbox_id"
|
||||
:on-toggle="onToggleContactPanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<dashboard-app-frame
|
||||
v-for="(dashboardApp, index) in dashboardApps"
|
||||
v-show="activeIndex - 1 === index"
|
||||
:key="currentChat.id + '-' + dashboardApp.id"
|
||||
:is-visible="activeIndex - 1 === index"
|
||||
:config="dashboardApps[index].content"
|
||||
:position="index"
|
||||
:current-chat="currentChat"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.conversation-details-wrap {
|
||||
@apply flex flex-col min-w-0 w-full;
|
||||
|
||||
@@ -1,120 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
||||
:class="{
|
||||
'active animate-card-select bg-slate-25 dark:bg-slate-800 border-woot-500':
|
||||
isActiveChat,
|
||||
'unread-chat': hasUnread,
|
||||
'has-inbox-name': showInboxName,
|
||||
'conversation-selected': selected,
|
||||
}"
|
||||
@mouseenter="onCardHover"
|
||||
@mouseleave="onCardLeave"
|
||||
@click="onCardClick"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
>
|
||||
<label v-if="hovered || selected" class="checkbox-wrapper" @click.stop>
|
||||
<input
|
||||
:value="selected"
|
||||
:checked="selected"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onSelectConversation($event.target.checked)"
|
||||
/>
|
||||
</label>
|
||||
<thumbnail
|
||||
v-if="bulkActionCheck"
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
:status="currentContact.availability_status"
|
||||
size="40px"
|
||||
/>
|
||||
<div
|
||||
class="px-0 py-3 border-b group-hover:border-transparent flex-1 border-slate-50 dark:border-slate-800/75 w-[calc(100%-40px)]"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||
<div class="flex gap-2 ml-2 rtl:mr-2 rtl:ml-0">
|
||||
<span
|
||||
v-if="showAssignee && assignee.name"
|
||||
class="text-slate-500 dark:text-slate-400 text-xs font-medium leading-3 py-0.5 px-0 inline-flex text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="person"
|
||||
size="12"
|
||||
class="text-slate-500 dark:text-slate-400"
|
||||
/>
|
||||
{{ assignee.name }}
|
||||
</span>
|
||||
<priority-mark :priority="chat.priority" />
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
class="conversation--user text-sm my-0 mx-2 capitalize pt-0.5 text-ellipsis font-medium overflow-hidden whitespace-nowrap w-[calc(100%-70px)] text-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{{ currentContact.name }}
|
||||
</h4>
|
||||
<message-preview
|
||||
v-if="lastMessageInChat"
|
||||
:message="lastMessageInChat"
|
||||
class="conversation--message my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm text-slate-700 dark:text-slate-200"
|
||||
/>
|
||||
<p
|
||||
v-else
|
||||
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<fluent-icon
|
||||
size="16"
|
||||
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
||||
icon="info"
|
||||
/>
|
||||
<span>
|
||||
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="absolute flex flex-col conversation--meta right-4 top-4">
|
||||
<span class="ml-auto font-normal leading-4 text-black-600 text-xxs">
|
||||
<time-ago
|
||||
:last-activity-timestamp="chat.timestamp"
|
||||
:created-at-timestamp="chat.created_at"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="unread shadow-lg rounded-full hidden text-xxs font-semibold h-4 leading-4 ml-auto mt-1 min-w-[1rem] px-1 py-0 text-center text-white bg-green-400"
|
||||
>
|
||||
{{ unreadCount > 9 ? '9+' : unreadCount }}
|
||||
</span>
|
||||
</div>
|
||||
<card-labels :conversation-id="chat.id" class="mt-0.5 mx-2 mb-0">
|
||||
<template v-if="hasSlaPolicyId" #before>
|
||||
<SLA-card-label :chat="chat" class="ltr:mr-1 rtl:ml-1" />
|
||||
</template>
|
||||
</card-labels>
|
||||
</div>
|
||||
<woot-context-menu
|
||||
v-if="showContextMenu"
|
||||
ref="menu"
|
||||
:x="contextMenu.x"
|
||||
:y="contextMenu.y"
|
||||
@close="closeContextMenu"
|
||||
>
|
||||
<conversation-context-menu
|
||||
:status="chat.status"
|
||||
:inbox-id="inbox.id"
|
||||
:priority="chat.priority"
|
||||
:chat-id="chat.id"
|
||||
:has-unread-messages="hasUnread"
|
||||
@update-conversation="onUpdateConversation"
|
||||
@assign-agent="onAssignAgent"
|
||||
@assign-label="onAssignLabel"
|
||||
@assign-team="onAssignTeam"
|
||||
@mark-as-unread="markAsUnread"
|
||||
@assign-priority="assignPriority"
|
||||
/>
|
||||
</woot-context-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from '../Thumbnail.vue';
|
||||
@@ -345,6 +228,123 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
||||
:class="{
|
||||
'active animate-card-select bg-slate-25 dark:bg-slate-800 border-woot-500':
|
||||
isActiveChat,
|
||||
'unread-chat': hasUnread,
|
||||
'has-inbox-name': showInboxName,
|
||||
'conversation-selected': selected,
|
||||
}"
|
||||
@mouseenter="onCardHover"
|
||||
@mouseleave="onCardLeave"
|
||||
@click="onCardClick"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
>
|
||||
<label v-if="hovered || selected" class="checkbox-wrapper" @click.stop>
|
||||
<input
|
||||
:value="selected"
|
||||
:checked="selected"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onSelectConversation($event.target.checked)"
|
||||
/>
|
||||
</label>
|
||||
<thumbnail
|
||||
v-if="bulkActionCheck"
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
:status="currentContact.availability_status"
|
||||
size="40px"
|
||||
/>
|
||||
<div
|
||||
class="px-0 py-3 border-b group-hover:border-transparent flex-1 border-slate-50 dark:border-slate-800/75 w-[calc(100%-40px)]"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||
<div class="flex gap-2 ml-2 rtl:mr-2 rtl:ml-0">
|
||||
<span
|
||||
v-if="showAssignee && assignee.name"
|
||||
class="text-slate-500 dark:text-slate-400 text-xs font-medium leading-3 py-0.5 px-0 inline-flex text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="person"
|
||||
size="12"
|
||||
class="text-slate-500 dark:text-slate-400"
|
||||
/>
|
||||
{{ assignee.name }}
|
||||
</span>
|
||||
<priority-mark :priority="chat.priority" />
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
class="conversation--user text-sm my-0 mx-2 capitalize pt-0.5 text-ellipsis font-medium overflow-hidden whitespace-nowrap w-[calc(100%-70px)] text-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{{ currentContact.name }}
|
||||
</h4>
|
||||
<message-preview
|
||||
v-if="lastMessageInChat"
|
||||
:message="lastMessageInChat"
|
||||
class="conversation--message my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm text-slate-700 dark:text-slate-200"
|
||||
/>
|
||||
<p
|
||||
v-else
|
||||
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<fluent-icon
|
||||
size="16"
|
||||
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
||||
icon="info"
|
||||
/>
|
||||
<span>
|
||||
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="absolute flex flex-col conversation--meta right-4 top-4">
|
||||
<span class="ml-auto font-normal leading-4 text-black-600 text-xxs">
|
||||
<time-ago
|
||||
:last-activity-timestamp="chat.timestamp"
|
||||
:created-at-timestamp="chat.created_at"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="unread shadow-lg rounded-full hidden text-xxs font-semibold h-4 leading-4 ml-auto mt-1 min-w-[1rem] px-1 py-0 text-center text-white bg-green-400"
|
||||
>
|
||||
{{ unreadCount > 9 ? '9+' : unreadCount }}
|
||||
</span>
|
||||
</div>
|
||||
<card-labels :conversation-id="chat.id" class="mt-0.5 mx-2 mb-0">
|
||||
<template v-if="hasSlaPolicyId" #before>
|
||||
<SLA-card-label :chat="chat" class="ltr:mr-1 rtl:ml-1" />
|
||||
</template>
|
||||
</card-labels>
|
||||
</div>
|
||||
<woot-context-menu
|
||||
v-if="showContextMenu"
|
||||
ref="menu"
|
||||
:x="contextMenu.x"
|
||||
:y="contextMenu.y"
|
||||
@close="closeContextMenu"
|
||||
>
|
||||
<conversation-context-menu
|
||||
:status="chat.status"
|
||||
:inbox-id="inbox.id"
|
||||
:priority="chat.priority"
|
||||
:chat-id="chat.id"
|
||||
:has-unread-messages="hasUnread"
|
||||
@update-conversation="onUpdateConversation"
|
||||
@assign-agent="onAssignAgent"
|
||||
@assign-label="onAssignLabel"
|
||||
@assign-team="onAssignTeam"
|
||||
@mark-as-unread="markAsUnread"
|
||||
@assign-priority="assignPriority"
|
||||
/>
|
||||
</woot-context-menu>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.conversation {
|
||||
&.unread-chat {
|
||||
|
||||
@@ -1,85 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between px-4 py-2 bg-white border-b dark:bg-slate-900 border-slate-50 dark:border-slate-800/50 md:flex-row"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center flex-1 w-full min-w-0"
|
||||
:class="isInboxView ? 'sm:flex-row' : 'md:flex-row'"
|
||||
>
|
||||
<div class="flex items-center justify-start max-w-full min-w-0 w-fit">
|
||||
<back-button
|
||||
v-if="showBackButton"
|
||||
:back-url="backButtonUrl"
|
||||
class="ltr:ml-0 rtl:mr-0 rtl:ml-4"
|
||||
/>
|
||||
<Thumbnail
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
:status="currentContact.availability_status"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col items-start min-w-0 ml-2 overflow-hidden rtl:ml-0 rtl:mr-2 w-fit"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row items-center max-w-full gap-1 p-0 m-0 w-fit"
|
||||
>
|
||||
<woot-button
|
||||
variant="link"
|
||||
color-scheme="secondary"
|
||||
class="[&>span]:overflow-hidden [&>span]:whitespace-nowrap [&>span]:text-ellipsis min-w-0"
|
||||
@click.prevent="$emit('contact-panel-toggle')"
|
||||
>
|
||||
<span
|
||||
class="text-base font-medium leading-tight text-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{{ currentContact.name }}
|
||||
</span>
|
||||
</woot-button>
|
||||
<fluent-icon
|
||||
v-if="!isHMACVerified"
|
||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||
size="14"
|
||||
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0 min-w-[14px]"
|
||||
icon="warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<inbox-name v-if="hasMultipleInboxes" :inbox="inbox" />
|
||||
<span
|
||||
v-if="isSnoozed"
|
||||
class="font-medium text-yellow-600 dark:text-yellow-500"
|
||||
>
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
<woot-button
|
||||
class="p-0"
|
||||
size="small"
|
||||
variant="link"
|
||||
@click="$emit('contact-panel-toggle')"
|
||||
>
|
||||
{{ contactPanelToggleText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-row items-center justify-end flex-grow gap-2 mt-3 header-actions-wrap lg:mt-0"
|
||||
:class="{ 'justify-end': isContactPanelOpen }"
|
||||
>
|
||||
<SLA-card-label v-if="hasSlaPolicyId" :chat="chat" show-extended-info />
|
||||
<linear
|
||||
v-if="isLinearIntegrationEnabled && isLinearFeatureEnabled"
|
||||
:conversation-id="currentChat.id"
|
||||
/>
|
||||
<more-actions :conversation-id="currentChat.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import agentMixin from '../../../mixins/agentMixin.js';
|
||||
@@ -212,6 +130,88 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between px-4 py-2 bg-white border-b dark:bg-slate-900 border-slate-50 dark:border-slate-800/50 md:flex-row"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center flex-1 w-full min-w-0"
|
||||
:class="isInboxView ? 'sm:flex-row' : 'md:flex-row'"
|
||||
>
|
||||
<div class="flex items-center justify-start max-w-full min-w-0 w-fit">
|
||||
<back-button
|
||||
v-if="showBackButton"
|
||||
:back-url="backButtonUrl"
|
||||
class="ltr:ml-0 rtl:mr-0 rtl:ml-4"
|
||||
/>
|
||||
<Thumbnail
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
:status="currentContact.availability_status"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col items-start min-w-0 ml-2 overflow-hidden rtl:ml-0 rtl:mr-2 w-fit"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row items-center max-w-full gap-1 p-0 m-0 w-fit"
|
||||
>
|
||||
<woot-button
|
||||
variant="link"
|
||||
color-scheme="secondary"
|
||||
class="[&>span]:overflow-hidden [&>span]:whitespace-nowrap [&>span]:text-ellipsis min-w-0"
|
||||
@click.prevent="$emit('contact-panel-toggle')"
|
||||
>
|
||||
<span
|
||||
class="text-base font-medium leading-tight text-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{{ currentContact.name }}
|
||||
</span>
|
||||
</woot-button>
|
||||
<fluent-icon
|
||||
v-if="!isHMACVerified"
|
||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||
size="14"
|
||||
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0 min-w-[14px]"
|
||||
icon="warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<inbox-name v-if="hasMultipleInboxes" :inbox="inbox" />
|
||||
<span
|
||||
v-if="isSnoozed"
|
||||
class="font-medium text-yellow-600 dark:text-yellow-500"
|
||||
>
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
<woot-button
|
||||
class="p-0"
|
||||
size="small"
|
||||
variant="link"
|
||||
@click="$emit('contact-panel-toggle')"
|
||||
>
|
||||
{{ contactPanelToggleText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-row items-center justify-end flex-grow gap-2 mt-3 header-actions-wrap lg:mt-0"
|
||||
:class="{ 'justify-end': isContactPanelOpen }"
|
||||
>
|
||||
<SLA-card-label v-if="hasSlaPolicyId" :chat="chat" show-extended-info />
|
||||
<linear
|
||||
v-if="isLinearIntegrationEnabled && isLinearFeatureEnabled"
|
||||
:conversation-id="currentChat.id"
|
||||
/>
|
||||
<more-actions :conversation-id="currentChat.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.conversation--header--actions {
|
||||
|
||||
@@ -1,80 +1,3 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onCancel">
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<woot-modal-header
|
||||
:header-title="$t('EMAIL_TRANSCRIPT.TITLE')"
|
||||
:header-content="$t('EMAIL_TRANSCRIPT.DESC')"
|
||||
/>
|
||||
<form class="w-full" @submit.prevent="onSubmit">
|
||||
<div class="w-full">
|
||||
<div
|
||||
v-if="currentChat.meta.sender && currentChat.meta.sender.email"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<input
|
||||
id="contact"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="contact"
|
||||
/>
|
||||
<label for="contact">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_CONTACT')
|
||||
}}</label>
|
||||
</div>
|
||||
<div v-if="currentChat.meta.assignee" class="flex items-center gap-2">
|
||||
<input
|
||||
id="assignee"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="assignee"
|
||||
/>
|
||||
<label for="assignee">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_AGENT')
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="other_email_address"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="other_email_address"
|
||||
/>
|
||||
<label for="other_email_address">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_OTHER_EMAIL_ADDRESS')
|
||||
}}</label>
|
||||
</div>
|
||||
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
|
||||
<label :class="{ error: v$.email.$error }">
|
||||
<input
|
||||
v-model.trim="email"
|
||||
type="text"
|
||||
:placeholder="$t('EMAIL_TRANSCRIPT.FORM.EMAIL.PLACEHOLDER')"
|
||||
@input="v$.email.$touch"
|
||||
/>
|
||||
<span v-if="v$.email.$error" class="message">
|
||||
{{ $t('EMAIL_TRANSCRIPT.FORM.EMAIL.ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||
<woot-submit-button
|
||||
:button-text="$t('EMAIL_TRANSCRIPT.SUBMIT')"
|
||||
:disabled="!isFormValid"
|
||||
/>
|
||||
<button class="button clear" @click.prevent="onCancel">
|
||||
{{ $t('EMAIL_TRANSCRIPT.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength, email } from '@vuelidate/validators';
|
||||
@@ -156,3 +79,80 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onCancel">
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<woot-modal-header
|
||||
:header-title="$t('EMAIL_TRANSCRIPT.TITLE')"
|
||||
:header-content="$t('EMAIL_TRANSCRIPT.DESC')"
|
||||
/>
|
||||
<form class="w-full" @submit.prevent="onSubmit">
|
||||
<div class="w-full">
|
||||
<div
|
||||
v-if="currentChat.meta.sender && currentChat.meta.sender.email"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<input
|
||||
id="contact"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="contact"
|
||||
/>
|
||||
<label for="contact">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_CONTACT')
|
||||
}}</label>
|
||||
</div>
|
||||
<div v-if="currentChat.meta.assignee" class="flex items-center gap-2">
|
||||
<input
|
||||
id="assignee"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="assignee"
|
||||
/>
|
||||
<label for="assignee">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_AGENT')
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="other_email_address"
|
||||
v-model="selectedType"
|
||||
type="radio"
|
||||
name="selectedType"
|
||||
value="other_email_address"
|
||||
/>
|
||||
<label for="other_email_address">{{
|
||||
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_OTHER_EMAIL_ADDRESS')
|
||||
}}</label>
|
||||
</div>
|
||||
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
|
||||
<label :class="{ error: v$.email.$error }">
|
||||
<input
|
||||
v-model.trim="email"
|
||||
type="text"
|
||||
:placeholder="$t('EMAIL_TRANSCRIPT.FORM.EMAIL.PLACEHOLDER')"
|
||||
@input="v$.email.$touch"
|
||||
/>
|
||||
<span v-if="v$.email.$error" class="message">
|
||||
{{ $t('EMAIL_TRANSCRIPT.FORM.EMAIL.ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||
<woot-submit-button
|
||||
:button-text="$t('EMAIL_TRANSCRIPT.SUBMIT')"
|
||||
:disabled="!isFormValid"
|
||||
/>
|
||||
<button class="button clear" @click.prevent="onCancel">
|
||||
{{ $t('EMAIL_TRANSCRIPT.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
+35
-35
@@ -1,38 +1,3 @@
|
||||
<template>
|
||||
<div :class="emptyClassName">
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching || loadingChatList"
|
||||
:message="loadingIndicatorMessage"
|
||||
/>
|
||||
<!-- No inboxes attached -->
|
||||
<div
|
||||
v-if="!inboxesList.length && !uiFlags.isFetching && !loadingChatList"
|
||||
class="clearfix"
|
||||
>
|
||||
<onboarding-view v-if="isAdmin" />
|
||||
<empty-state-message
|
||||
v-else
|
||||
:message="$t('CONVERSATION.NO_INBOX_AGENT')"
|
||||
/>
|
||||
</div>
|
||||
<!-- Show empty state images if not loading -->
|
||||
|
||||
<div
|
||||
v-else-if="!uiFlags.isFetching && !loadingChatList"
|
||||
class="flex flex-col items-center justify-center h-full"
|
||||
>
|
||||
<!-- No conversations available -->
|
||||
<empty-state-message
|
||||
v-if="!allConversations.length"
|
||||
:message="$t('CONVERSATION.NO_MESSAGE_1')"
|
||||
/>
|
||||
<empty-state-message
|
||||
v-else-if="allConversations.length && !currentChat.id"
|
||||
:message="conversationMissingMessage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
@@ -95,3 +60,38 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :class="emptyClassName">
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching || loadingChatList"
|
||||
:message="loadingIndicatorMessage"
|
||||
/>
|
||||
<!-- No inboxes attached -->
|
||||
<div
|
||||
v-if="!inboxesList.length && !uiFlags.isFetching && !loadingChatList"
|
||||
class="clearfix"
|
||||
>
|
||||
<onboarding-view v-if="isAdmin" />
|
||||
<empty-state-message
|
||||
v-else
|
||||
:message="$t('CONVERSATION.NO_INBOX_AGENT')"
|
||||
/>
|
||||
</div>
|
||||
<!-- Show empty state images if not loading -->
|
||||
|
||||
<div
|
||||
v-else-if="!uiFlags.isFetching && !loadingChatList"
|
||||
class="flex flex-col items-center justify-center h-full"
|
||||
>
|
||||
<!-- No conversations available -->
|
||||
<empty-state-message
|
||||
v-if="!allConversations.length"
|
||||
:message="$t('CONVERSATION.NO_MESSAGE_1')"
|
||||
/>
|
||||
<empty-state-message
|
||||
v-else-if="allConversations.length && !currentChat.id"
|
||||
:message="conversationMissingMessage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+13
-13
@@ -1,3 +1,16 @@
|
||||
<script>
|
||||
import FeaturePlaceholder from './FeaturePlaceholder.vue';
|
||||
export default {
|
||||
components: { FeaturePlaceholder },
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<img
|
||||
@@ -20,16 +33,3 @@
|
||||
<feature-placeholder />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeaturePlaceholder from './FeaturePlaceholder.vue';
|
||||
export default {
|
||||
components: { FeaturePlaceholder },
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+24
-24
@@ -1,3 +1,27 @@
|
||||
<script>
|
||||
import Hotkey from 'dashboard/components/base/Hotkey.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Hotkey,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyShortcuts: [
|
||||
{
|
||||
key: 'K',
|
||||
description: this.$t('CONVERSATION.EMPTY_STATE.CMD_BAR'),
|
||||
},
|
||||
{
|
||||
key: '/',
|
||||
description: this.$t('CONVERSATION.EMPTY_STATE.KEYBOARD_SHORTCUTS'),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 mt-9">
|
||||
<div
|
||||
@@ -25,27 +49,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Hotkey from 'dashboard/components/base/Hotkey.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Hotkey,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyShortcuts: [
|
||||
{
|
||||
key: 'K',
|
||||
description: this.$t('CONVERSATION.EMPTY_STATE.CMD_BAR'),
|
||||
},
|
||||
{
|
||||
key: '/',
|
||||
description: this.$t('CONVERSATION.EMPTY_STATE.KEYBOARD_SHORTCUTS'),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<select
|
||||
v-model="activeValue"
|
||||
class="bg-slate-25 dark:bg-slate-700 text-xs h-6 my-0 mx-1 py-0 pr-6 pl-2 w-32 border border-solid border-slate-75 dark:border-slate-600 text-slate-800 dark:text-slate-100"
|
||||
@change="onTabChange()"
|
||||
>
|
||||
<option v-for="(value, status) in items" :key="status" :value="status">
|
||||
{{ $t(`${pathPrefix}.${status}.TEXT`) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
@@ -46,3 +35,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<select
|
||||
v-model="activeValue"
|
||||
class="bg-slate-25 dark:bg-slate-700 text-xs h-6 my-0 mx-1 py-0 pr-6 pl-2 w-32 border border-solid border-slate-75 dark:border-slate-600 text-slate-800 dark:text-slate-100"
|
||||
@change="onTabChange()"
|
||||
>
|
||||
<option v-for="(value, status) in items" :key="status" :value="status">
|
||||
{{ $t(`${pathPrefix}.${status}.TEXT`) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
@@ -1,147 +1,3 @@
|
||||
<template>
|
||||
<li
|
||||
v-if="shouldRenderMessage"
|
||||
:id="`message${data.id}`"
|
||||
:class="[alignBubble, 'group']"
|
||||
>
|
||||
<div :class="wrapClass">
|
||||
<div
|
||||
v-if="isFailed && !hasOneDayPassed && !isAnEmailInbox"
|
||||
class="message-failed--alert"
|
||||
>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
||||
size="tiny"
|
||||
color-scheme="alert"
|
||||
variant="clear"
|
||||
icon="arrow-clockwise"
|
||||
@click="retrySendMessage"
|
||||
/>
|
||||
</div>
|
||||
<div :class="bubbleClass" @contextmenu="openContextMenu($event)">
|
||||
<bubble-mail-head
|
||||
:email-attributes="contentAttributes.email"
|
||||
:cc="emailHeadAttributes.cc"
|
||||
:bcc="emailHeadAttributes.bcc"
|
||||
:is-incoming="isIncoming"
|
||||
/>
|
||||
<instagram-story-reply v-if="storyUrl" :story-url="storyUrl" />
|
||||
<bubble-reply-to
|
||||
v-if="inReplyToMessageId && inboxSupportsReplyTo.incoming"
|
||||
:message="inReplyTo"
|
||||
:message-type="data.message_type"
|
||||
:parent-has-attachments="hasAttachments"
|
||||
/>
|
||||
<div v-if="isUnsupported">
|
||||
<template v-if="isAFacebookInbox && isInstagram">
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_INSTAGRAM') }}
|
||||
</template>
|
||||
<template v-else-if="isAFacebookInbox">
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_FACEBOOK') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE') }}
|
||||
</template>
|
||||
</div>
|
||||
<bubble-text
|
||||
v-else-if="data.content"
|
||||
:message="message"
|
||||
:is-email="isEmailContentType"
|
||||
:display-quoted-button="displayQuotedButton"
|
||||
/>
|
||||
<bubble-integration
|
||||
:message-id="data.id"
|
||||
:content-attributes="contentAttributes"
|
||||
:inbox-id="data.inbox_id"
|
||||
/>
|
||||
<span
|
||||
v-if="isPending && hasAttachments"
|
||||
class="chat-bubble has-attachment agent"
|
||||
>
|
||||
{{ $t('CONVERSATION.UPLOADING_ATTACHMENTS') }}
|
||||
</span>
|
||||
<div v-if="!isPending && hasAttachments">
|
||||
<div v-for="attachment in attachments" :key="attachment.id">
|
||||
<instagram-story
|
||||
v-if="isAnInstagramStory"
|
||||
:story-url="attachment.data_url"
|
||||
@error="onMediaLoadError"
|
||||
/>
|
||||
<bubble-image-audio-video
|
||||
v-else-if="isAttachmentImageVideoAudio(attachment.file_type)"
|
||||
:attachment="attachment"
|
||||
@error="onMediaLoadError"
|
||||
/>
|
||||
<bubble-location
|
||||
v-else-if="attachment.file_type === 'location'"
|
||||
:latitude="attachment.coordinates_lat"
|
||||
:longitude="attachment.coordinates_long"
|
||||
:name="attachment.fallback_title"
|
||||
/>
|
||||
<bubble-contact
|
||||
v-else-if="attachment.file_type === 'contact'"
|
||||
:name="data.content"
|
||||
:phone-number="attachment.fallback_title"
|
||||
/>
|
||||
<bubble-file v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
<bubble-actions
|
||||
:id="data.id"
|
||||
:sender="data.sender"
|
||||
:story-sender="storySender"
|
||||
:external-error="errorMessageTooltip"
|
||||
:story-id="`${storyId}`"
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:is-email="isEmailContentType"
|
||||
:is-private="data.private"
|
||||
:message-type="data.message_type"
|
||||
:message-status="status"
|
||||
:source-id="data.source_id"
|
||||
:inbox-id="data.inbox_id"
|
||||
:created-at="createdAt"
|
||||
/>
|
||||
</div>
|
||||
<spinner v-if="isPending" size="tiny" />
|
||||
<div
|
||||
v-if="showAvatar"
|
||||
v-tooltip.left="tooltipForSender"
|
||||
class="sender--info"
|
||||
>
|
||||
<woot-thumbnail
|
||||
:src="sender.thumbnail"
|
||||
:username="senderNameForAvatar"
|
||||
size="16px"
|
||||
/>
|
||||
<a
|
||||
v-if="isATweet && isIncoming"
|
||||
class="sender--available-name"
|
||||
:href="twitterProfileLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
{{ sender.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldShowContextMenu"
|
||||
class="invisible context-menu-wrap group-hover:visible"
|
||||
>
|
||||
<context-menu
|
||||
v-if="isBubble && !isMessageDeleted"
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:is-open="showContextMenu"
|
||||
:enabled-options="contextMenuEnabledOptions"
|
||||
:message="data"
|
||||
@open="openContextMenu"
|
||||
@close="closeContextMenu"
|
||||
@replyTo="handleReplyTo"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import BubbleActions from './bubble/Actions.vue';
|
||||
@@ -582,6 +438,150 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<li
|
||||
v-if="shouldRenderMessage"
|
||||
:id="`message${data.id}`"
|
||||
:class="[alignBubble, 'group']"
|
||||
>
|
||||
<div :class="wrapClass">
|
||||
<div
|
||||
v-if="isFailed && !hasOneDayPassed && !isAnEmailInbox"
|
||||
class="message-failed--alert"
|
||||
>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
||||
size="tiny"
|
||||
color-scheme="alert"
|
||||
variant="clear"
|
||||
icon="arrow-clockwise"
|
||||
@click="retrySendMessage"
|
||||
/>
|
||||
</div>
|
||||
<div :class="bubbleClass" @contextmenu="openContextMenu($event)">
|
||||
<bubble-mail-head
|
||||
:email-attributes="contentAttributes.email"
|
||||
:cc="emailHeadAttributes.cc"
|
||||
:bcc="emailHeadAttributes.bcc"
|
||||
:is-incoming="isIncoming"
|
||||
/>
|
||||
<instagram-story-reply v-if="storyUrl" :story-url="storyUrl" />
|
||||
<bubble-reply-to
|
||||
v-if="inReplyToMessageId && inboxSupportsReplyTo.incoming"
|
||||
:message="inReplyTo"
|
||||
:message-type="data.message_type"
|
||||
:parent-has-attachments="hasAttachments"
|
||||
/>
|
||||
<div v-if="isUnsupported">
|
||||
<template v-if="isAFacebookInbox && isInstagram">
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_INSTAGRAM') }}
|
||||
</template>
|
||||
<template v-else-if="isAFacebookInbox">
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_FACEBOOK') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE') }}
|
||||
</template>
|
||||
</div>
|
||||
<bubble-text
|
||||
v-else-if="data.content"
|
||||
:message="message"
|
||||
:is-email="isEmailContentType"
|
||||
:display-quoted-button="displayQuotedButton"
|
||||
/>
|
||||
<bubble-integration
|
||||
:message-id="data.id"
|
||||
:content-attributes="contentAttributes"
|
||||
:inbox-id="data.inbox_id"
|
||||
/>
|
||||
<span
|
||||
v-if="isPending && hasAttachments"
|
||||
class="chat-bubble has-attachment agent"
|
||||
>
|
||||
{{ $t('CONVERSATION.UPLOADING_ATTACHMENTS') }}
|
||||
</span>
|
||||
<div v-if="!isPending && hasAttachments">
|
||||
<div v-for="attachment in attachments" :key="attachment.id">
|
||||
<instagram-story
|
||||
v-if="isAnInstagramStory"
|
||||
:story-url="attachment.data_url"
|
||||
@error="onMediaLoadError"
|
||||
/>
|
||||
<bubble-image-audio-video
|
||||
v-else-if="isAttachmentImageVideoAudio(attachment.file_type)"
|
||||
:attachment="attachment"
|
||||
@error="onMediaLoadError"
|
||||
/>
|
||||
<bubble-location
|
||||
v-else-if="attachment.file_type === 'location'"
|
||||
:latitude="attachment.coordinates_lat"
|
||||
:longitude="attachment.coordinates_long"
|
||||
:name="attachment.fallback_title"
|
||||
/>
|
||||
<bubble-contact
|
||||
v-else-if="attachment.file_type === 'contact'"
|
||||
:name="data.content"
|
||||
:phone-number="attachment.fallback_title"
|
||||
/>
|
||||
<bubble-file v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
<bubble-actions
|
||||
:id="data.id"
|
||||
:sender="data.sender"
|
||||
:story-sender="storySender"
|
||||
:external-error="errorMessageTooltip"
|
||||
:story-id="`${storyId}`"
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:is-email="isEmailContentType"
|
||||
:is-private="data.private"
|
||||
:message-type="data.message_type"
|
||||
:message-status="status"
|
||||
:source-id="data.source_id"
|
||||
:inbox-id="data.inbox_id"
|
||||
:created-at="createdAt"
|
||||
/>
|
||||
</div>
|
||||
<spinner v-if="isPending" size="tiny" />
|
||||
<div
|
||||
v-if="showAvatar"
|
||||
v-tooltip.left="tooltipForSender"
|
||||
class="sender--info"
|
||||
>
|
||||
<woot-thumbnail
|
||||
:src="sender.thumbnail"
|
||||
:username="senderNameForAvatar"
|
||||
size="16px"
|
||||
/>
|
||||
<a
|
||||
v-if="isATweet && isIncoming"
|
||||
class="sender--available-name"
|
||||
:href="twitterProfileLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
{{ sender.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldShowContextMenu"
|
||||
class="invisible context-menu-wrap group-hover:visible"
|
||||
>
|
||||
<context-menu
|
||||
v-if="isBubble && !isMessageDeleted"
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:is-open="showContextMenu"
|
||||
:enabled-options="contextMenuEnabledOptions"
|
||||
:message="data"
|
||||
@open="openContextMenu"
|
||||
@close="closeContextMenu"
|
||||
@replyTo="handleReplyTo"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.wrap {
|
||||
> .bubble {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user