feat: Update Captain pages UI (#13184)
This commit is contained in:
@@ -37,7 +37,7 @@ const updateCurrentPage = page => {
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</main>
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-0 px-4 pb-4">
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-0">
|
||||
<PaginationFooter
|
||||
current-page-info="COMPANIES_LAYOUT.PAGINATION_FOOTER.SHOWING"
|
||||
:current-page="currentPage"
|
||||
|
||||
@@ -100,7 +100,7 @@ const togglePortalSwitcher = () => {
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</main>
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-10 px-4 pb-4">
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-10">
|
||||
<PaginationFooter
|
||||
:current-page="currentPage"
|
||||
:total-items="totalItems"
|
||||
|
||||
@@ -20,11 +20,11 @@ const emit = defineEmits(['action']);
|
||||
|
||||
const bannerClass = computed(() => {
|
||||
const classMap = {
|
||||
slate: 'bg-n-slate-3 border-n-slate-4 text-n-slate-11',
|
||||
amber: 'bg-n-amber-3 border-n-amber-4 text-n-amber-11',
|
||||
teal: 'bg-n-teal-3 border-n-teal-4 text-n-teal-11',
|
||||
ruby: 'bg-n-ruby-3 border-n-ruby-4 text-n-ruby-11',
|
||||
blue: 'bg-n-blue-3 border-n-blue-4 text-n-blue-11',
|
||||
slate: 'bg-n-slate-1 outline-n-slate-5 text-n-slate-12',
|
||||
amber: 'bg-n-amber-1 outline-n-amber-5 text-n-amber-12',
|
||||
teal: 'bg-n-teal-1 outline-n-teal-5 text-n-teal-12',
|
||||
ruby: 'bg-n-ruby-1 outline-n-ruby-5 text-n-ruby-12',
|
||||
blue: 'bg-n-blue-1 outline-n-blue-5 text-n-blue-12',
|
||||
};
|
||||
|
||||
return classMap[props.color];
|
||||
@@ -32,11 +32,11 @@ const bannerClass = computed(() => {
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
const classMap = {
|
||||
slate: 'bg-n-slate-4 hover:bg-n-slate-5 text-n-slate-11',
|
||||
amber: 'bg-n-amber-4 hover:bg-n-amber-5 text-n-amber-11',
|
||||
teal: 'bg-n-teal-4 hover:bg-n-teal-5 text-n-teal-11',
|
||||
ruby: 'bg-n-ruby-4 hover:bg-n-ruby-5 text-n-ruby-11',
|
||||
blue: 'bg-n-blue-4 hover:bg-n-blue-5 text-n-blue-11',
|
||||
slate: 'hover:bg-n-slate-4 text-n-slate-11',
|
||||
amber: 'hover:bg-n-amber-4 text-n-amber-11',
|
||||
teal: 'hover:bg-n-teal-4 text-n-teal-11',
|
||||
ruby: 'hover:bg-n-ruby-4 text-n-ruby-11',
|
||||
blue: 'hover:bg-n-blue-4 text-n-blue-11',
|
||||
};
|
||||
|
||||
return classMap[props.color];
|
||||
@@ -49,7 +49,7 @@ const triggerAction = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="text-sm rounded-xl flex items-center justify-between gap-2 border"
|
||||
class="text-sm rounded-xl flex items-center justify-between min-h-12 gap-2 outline outline-1 -outline-offset-1"
|
||||
:class="[
|
||||
bannerClass,
|
||||
{
|
||||
@@ -64,11 +64,11 @@ const triggerAction = () => {
|
||||
<div>
|
||||
<button
|
||||
v-if="actionLabel"
|
||||
class="px-3 py-1 w-auto grid place-content-center rounded-lg"
|
||||
class="px-3 py-1 w-auto flex items-center justify-center rounded-lg flex-shrink-0"
|
||||
:class="buttonClass"
|
||||
@click="triggerAction"
|
||||
>
|
||||
{{ actionLabel }}
|
||||
<span class="line-clamp-2 text-button">{{ actionLabel }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +130,7 @@ const handleCreateAssistant = () => {
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="!isFetchingAssistants"
|
||||
class="text-xl font-medium truncate text-n-slate-12"
|
||||
class="text-heading-1 truncate text-n-slate-12"
|
||||
>
|
||||
{{ activeAssistantName }}
|
||||
</span>
|
||||
@@ -162,11 +162,16 @@ const handleCreateAssistant = () => {
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
v-if="showAssistantSwitcher && !showPaywall && headerTitle"
|
||||
class="w-0.5 h-4 rounded-2xl bg-n-weak"
|
||||
class="w-px h-3 rounded-xl bg-n-weak"
|
||||
/>
|
||||
<span
|
||||
v-if="headerTitle"
|
||||
class="text-xl font-medium text-n-slate-12"
|
||||
class="text-n-slate-12"
|
||||
:class="
|
||||
showAssistantSwitcher && !showPaywall
|
||||
? 'text-body-main'
|
||||
: 'text-heading-1'
|
||||
"
|
||||
>
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
@@ -174,7 +179,7 @@ const handleCreateAssistant = () => {
|
||||
v-if="!isEmpty && showKnowMore"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<div class="w-0.5 h-4 rounded-2xl bg-n-weak" />
|
||||
<div class="w-px h-3 rounded-xl bg-n-weak" />
|
||||
<slot name="knowMore" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -222,7 +227,7 @@ const handleCreateAssistant = () => {
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-10 px-4 pb-4">
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-10">
|
||||
<PaginationFooter
|
||||
:current-page="currentPage"
|
||||
:total-items="totalCount"
|
||||
|
||||
@@ -20,6 +20,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'Delete',
|
||||
},
|
||||
animationDirection: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
validator: value => ['horizontal', 'vertical'].includes(value),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['bulkDelete']);
|
||||
@@ -49,19 +54,32 @@ const bulkCheckboxState = computed({
|
||||
modelValue.value = newSelectedIds;
|
||||
},
|
||||
});
|
||||
|
||||
const animationClasses = computed(() => {
|
||||
if (props.animationDirection === 'vertical') {
|
||||
return {
|
||||
enterFrom: 'opacity-0 transform -translate-y-4',
|
||||
enterTo: 'opacity-100 transform translate-y-0',
|
||||
};
|
||||
}
|
||||
return {
|
||||
enterFrom: 'opacity-0 transform ltr:-translate-x-4 rtl:translate-x-4',
|
||||
enterTo: 'opacity-100 transform translate-x-0',
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition
|
||||
name="slide-fade"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 transform ltr:-translate-x-4 rtl:translate-x-4"
|
||||
enter-to-class="opacity-100 transform translate-x-0"
|
||||
:enter-from-class="animationClasses.enterFrom"
|
||||
:enter-to-class="animationClasses.enterTo"
|
||||
leave-active-class="hidden opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="hasSelected"
|
||||
class="flex items-center gap-3 py-1 ltr:pl-3 rtl:pr-3 ltr:pr-4 rtl:pl-4 rounded-lg bg-n-solid-2 outline outline-1 outline-n-container shadow"
|
||||
class="flex items-center gap-3 py-2 ltr:pl-3 rtl:pr-3 ltr:pr-2 rtl:pl-2 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container -outline-offset-1 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-1.5 min-w-0">
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
import {
|
||||
isPdfDocument,
|
||||
formatDocumentLink,
|
||||
} from 'shared/helpers/documentHelper';
|
||||
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -37,88 +35,59 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(['action']);
|
||||
const { checkPermissions } = usePolicy();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const [showActionsDropdown, toggleDropdown] = useToggle();
|
||||
|
||||
const menuItems = computed(() => {
|
||||
const allOptions = [
|
||||
{
|
||||
label: t('CAPTAIN.DOCUMENTS.OPTIONS.VIEW_RELATED_RESPONSES'),
|
||||
value: 'viewRelatedQuestions',
|
||||
action: 'viewRelatedQuestions',
|
||||
icon: 'i-ph-tree-view-duotone',
|
||||
},
|
||||
];
|
||||
|
||||
if (checkPermissions(['administrator'])) {
|
||||
allOptions.push({
|
||||
label: t('CAPTAIN.DOCUMENTS.OPTIONS.DELETE_DOCUMENT'),
|
||||
value: 'delete',
|
||||
action: 'delete',
|
||||
icon: 'i-lucide-trash',
|
||||
});
|
||||
}
|
||||
|
||||
return allOptions;
|
||||
});
|
||||
|
||||
const createdAt = computed(() => dynamicTime(props.createdAt));
|
||||
|
||||
const displayLink = computed(() => formatDocumentLink(props.externalLink));
|
||||
const linkIcon = computed(() =>
|
||||
isPdfDocument(props.externalLink) ? 'i-ph-file-pdf' : 'i-ph-link-simple'
|
||||
isPdfDocument(props.externalLink) ? 'i-lucide-file-text' : 'i-lucide-link'
|
||||
);
|
||||
|
||||
const handleAction = ({ action, value }) => {
|
||||
toggleDropdown(false);
|
||||
const handleAction = (action, value) => {
|
||||
emit('action', { action, value, id: props.id });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardLayout>
|
||||
<CardLayout class="[&>div]:px-5 [&>div]:gap-2">
|
||||
<div class="flex gap-1 justify-between w-full">
|
||||
<span class="text-base text-n-slate-12 line-clamp-1">
|
||||
<span class="text-heading-3 text-n-slate-12 line-clamp-1">
|
||||
{{ name }}
|
||||
</span>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
class="flex relative items-center group"
|
||||
>
|
||||
<Button
|
||||
icon="i-woot-menu-list"
|
||||
slate
|
||||
sm
|
||||
ghost
|
||||
@click="handleAction('viewRelatedQuestions', 'viewRelatedQuestions')"
|
||||
/>
|
||||
<div class="w-px h-3 bg-n-weak rounded-lg" />
|
||||
<Policy :permissions="['administrator']">
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
@click="toggleDropdown()"
|
||||
icon="i-woot-bin"
|
||||
slate
|
||||
sm
|
||||
ghost
|
||||
@click="handleAction('delete', 'delete')"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="menuItems"
|
||||
class="top-full mt-1 ltr:right-0 rtl:left-0 xl:ltr:right-0 xl:rtl:left-0"
|
||||
@action="handleAction($event)"
|
||||
/>
|
||||
</div>
|
||||
</Policy>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4 justify-between items-center w-full">
|
||||
<span
|
||||
class="flex gap-1 items-center text-sm truncate shrink-0 text-n-slate-11"
|
||||
>
|
||||
<i class="i-woot-captain" />
|
||||
<Icon icon="i-woot-captain" />
|
||||
{{ assistant?.name || '' }}
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 gap-1 justify-start items-center text-sm truncate text-n-slate-11"
|
||||
>
|
||||
<i :class="linkIcon" class="shrink-0" />
|
||||
<span class="truncate">{{ displayLink }}</span>
|
||||
<Icon :icon="linkIcon" class="shrink-0" />
|
||||
<span class="truncate text-body-main">{{ displayLink }}</span>
|
||||
</span>
|
||||
<div class="text-sm shrink-0 text-n-slate-11 line-clamp-1">
|
||||
<div class="text-label-small shrink-0 text-n-slate-11 line-clamp-1">
|
||||
{{ createdAt }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import { INBOX_TYPES, getInboxIconByType } from 'dashboard/helper/inbox';
|
||||
import ChannelIcon from 'dashboard/components-next/icon/ChannelIcon.vue';
|
||||
import { INBOX_TYPES, getChannelTypeDisplayName } from 'dashboard/helper/inbox';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -23,10 +20,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['action']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const [showActionsDropdown, toggleDropdown] = useToggle();
|
||||
|
||||
const inboxName = computed(() => {
|
||||
const inbox = props.inbox;
|
||||
if (!inbox?.name) {
|
||||
@@ -49,56 +42,47 @@ const inboxName = computed(() => {
|
||||
return inbox.name;
|
||||
});
|
||||
|
||||
const menuItems = computed(() => [
|
||||
{
|
||||
label: t('CAPTAIN.INBOXES.OPTIONS.DISCONNECT'),
|
||||
value: 'delete',
|
||||
action: 'delete',
|
||||
icon: 'i-lucide-trash',
|
||||
},
|
||||
]);
|
||||
|
||||
const icon = computed(() => {
|
||||
const { medium, channel_type: type } = props.inbox;
|
||||
return getInboxIconByType(type, medium, 'line');
|
||||
const channelType = computed(() => {
|
||||
const { channel_type: type, medium } = props.inbox;
|
||||
return getChannelTypeDisplayName(type, medium);
|
||||
});
|
||||
|
||||
const handleAction = ({ action, value }) => {
|
||||
toggleDropdown(false);
|
||||
const handleAction = (action, value) => {
|
||||
emit('action', { action, value, id: props.id });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardLayout>
|
||||
<CardLayout
|
||||
class="ltr:[&>div]:pl-4 ltr:[&>div]:pr-3 rtl:[&>div]:pl-3 rtl:[&>div]:pr-4 [&>div]:gap-2 [&>div]:py-4"
|
||||
>
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span
|
||||
class="text-base text-n-slate-12 line-clamp-1 flex items-center gap-2"
|
||||
>
|
||||
<Icon :icon="icon" class="size-5" />
|
||||
{{ inboxName }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<Policy
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
:permissions="['administrator']"
|
||||
class="relative flex items-center group"
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div
|
||||
class="size-8 rounded-[0.625rem] flex items-center justify-center outline outline-1 outline-n-weak -outline-offset-1 flex-shrink-0"
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
@click="toggleDropdown()"
|
||||
<ChannelIcon
|
||||
:inbox="inbox"
|
||||
class="size-4 flex-shrink-0 text-n-slate-11"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="menuItems"
|
||||
class="mt-1 ltr:right-0 rtl:left-0 top-full"
|
||||
@action="handleAction($event)"
|
||||
/>
|
||||
</Policy>
|
||||
</div>
|
||||
<span class="text-heading-3 text-n-slate-12 line-clamp-1 min-w-0">
|
||||
{{ inboxName }}
|
||||
</span>
|
||||
<div class="w-px h-3 bg-n-weak rounded-lg flex-shrink-0" />
|
||||
<span class="text-body-main text-n-slate-11 flex-shrink-0">{{
|
||||
channelType
|
||||
}}</span>
|
||||
</div>
|
||||
<Policy :permissions="['administrator']">
|
||||
<Button
|
||||
icon="i-lucide-unlink"
|
||||
slate
|
||||
sm
|
||||
ghost
|
||||
@click="handleAction('delete', 'delete')"
|
||||
/>
|
||||
</Policy>
|
||||
</div>
|
||||
</CardLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||
|
||||
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
@@ -36,10 +32,6 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
assistant: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
updatedAt: {
|
||||
type: Number,
|
||||
required: true,
|
||||
@@ -52,10 +44,6 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@@ -66,53 +54,18 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['action', 'navigate', 'select', 'hover']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const [showActionsDropdown, toggleDropdown] = useToggle();
|
||||
const emit = defineEmits(['action', 'navigate', 'select']);
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.isSelected,
|
||||
set: () => emit('select', props.id),
|
||||
});
|
||||
|
||||
const statusAction = computed(() => {
|
||||
if (props.status === 'pending') {
|
||||
return [
|
||||
{
|
||||
label: t('CAPTAIN.RESPONSES.OPTIONS.APPROVE'),
|
||||
value: 'approve',
|
||||
action: 'approve',
|
||||
icon: 'i-lucide-circle-check-big',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const menuItems = computed(() => [
|
||||
...statusAction.value,
|
||||
{
|
||||
label: t('CAPTAIN.RESPONSES.OPTIONS.EDIT_RESPONSE'),
|
||||
value: 'edit',
|
||||
action: 'edit',
|
||||
icon: 'i-lucide-pencil-line',
|
||||
},
|
||||
{
|
||||
label: t('CAPTAIN.RESPONSES.OPTIONS.DELETE_RESPONSE'),
|
||||
value: 'delete',
|
||||
action: 'delete',
|
||||
icon: 'i-lucide-trash',
|
||||
},
|
||||
]);
|
||||
|
||||
const timestamp = computed(() =>
|
||||
dynamicTime(props.updatedAt || props.createdAt)
|
||||
);
|
||||
|
||||
const handleAssistantAction = ({ action, value }) => {
|
||||
toggleDropdown(false);
|
||||
const handleAssistantAction = (action, value) => {
|
||||
emit('action', { action, value, id: props.id });
|
||||
};
|
||||
|
||||
@@ -125,43 +78,103 @@ const handleDocumentableClick = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardLayout
|
||||
selectable
|
||||
class="relative"
|
||||
:class="{ 'rounded-md': compact }"
|
||||
@mouseenter="emit('hover', true)"
|
||||
@mouseleave="emit('hover', false)"
|
||||
>
|
||||
<div v-show="selectable" class="absolute top-7 ltr:left-3 rtl:right-3">
|
||||
<Checkbox v-model="modelValue" />
|
||||
</div>
|
||||
<div class="flex relative justify-between w-full gap-1">
|
||||
<span class="text-base text-n-slate-12 line-clamp-1">
|
||||
{{ question }}
|
||||
</span>
|
||||
<div v-if="!compact && showMenu" class="flex items-center gap-2">
|
||||
<div class="py-4 flex flex-col gap-2">
|
||||
<div
|
||||
v-if="!compact"
|
||||
class="flex items-start gap-3 h-6"
|
||||
:class="{ 'justify-between w-full': !showActions }"
|
||||
>
|
||||
<div class="inline-flex items-center gap-2 min-w-0">
|
||||
<!-- <span
|
||||
v-if="status === 'approved'"
|
||||
class="text-sm shrink-0 truncate text-n-slate-11 inline-flex items-center gap-1"
|
||||
>
|
||||
<Icon icon="i-woot-captain" class="size-3.5" />
|
||||
{{ assistant?.name || '' }}
|
||||
</span> -->
|
||||
<div
|
||||
v-if="documentable"
|
||||
class="text-sm text-n-slate-11 grid grid-cols-[auto_1fr] items-center gap-1.5 min-w-0"
|
||||
>
|
||||
<Icon
|
||||
v-if="documentable.type === 'Captain::Document'"
|
||||
icon="i-lucide-files"
|
||||
class="size-3.5 flex-shrink-0"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="documentable.type === 'User'"
|
||||
icon="i-lucide-circle-user-round"
|
||||
class="size-3.5 flex-shrink-0"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="documentable.type === 'Conversation'"
|
||||
icon="i-lucide-message-circle"
|
||||
class="size-3.5 flex-shrink-0"
|
||||
/>
|
||||
<span
|
||||
v-if="documentable.type === 'Captain::Document'"
|
||||
class="truncate text-body-main text-n-slate-11"
|
||||
:title="documentable.name"
|
||||
>
|
||||
{{ documentable.name }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="documentable.type === 'User'"
|
||||
class="truncate text-body-main text-n-slate-11"
|
||||
:title="documentable.available_name"
|
||||
>
|
||||
{{ documentable.available_name }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="documentable.type === 'Conversation'"
|
||||
class="hover:underline truncate cursor-pointer text-body-main text-n-slate-11"
|
||||
role="button"
|
||||
@click="handleDocumentableClick"
|
||||
>
|
||||
{{ documentable.display_id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-px h-3 rounded-lg bg-n-strong" />
|
||||
<span class="shrink-0 text-body-main text-n-slate-11">
|
||||
{{ timestamp }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!compact && showMenu">
|
||||
<Policy
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
:permissions="['administrator']"
|
||||
class="relative flex items-center group"
|
||||
class="gap-2 flex items-center flex-shrink-0"
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
@click="toggleDropdown()"
|
||||
icon="i-lucide-pencil-line"
|
||||
slate
|
||||
sm
|
||||
outline
|
||||
@click="handleAssistantAction('edit', 'edit')"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="menuItems"
|
||||
class="mt-1 ltr:right-0 rtl:right-0 top-full"
|
||||
@action="handleAssistantAction($event)"
|
||||
<Button
|
||||
icon="i-lucide-trash"
|
||||
slate
|
||||
sm
|
||||
outline
|
||||
@click="handleAssistantAction('delete', 'delete')"
|
||||
/>
|
||||
</Policy>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-n-slate-11 text-sm line-clamp-5">
|
||||
<div class="flex justify-between items-center w-full gap-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div v-if="!compact" class="size-5 flex items-center">
|
||||
<Checkbox v-model="modelValue" />
|
||||
</div>
|
||||
<span class="text-n-slate-12 text-heading-3 line-clamp-1">
|
||||
{{ question }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="text-n-slate-11 text-body-main line-clamp-5"
|
||||
:class="{ 'ltr:pl-[1.625rem] rtl:pr-[1.625rem]': !compact }"
|
||||
>
|
||||
{{ answer }}
|
||||
</span>
|
||||
<div
|
||||
@@ -177,9 +190,7 @@ const handleDocumentableClick = () => {
|
||||
sm
|
||||
link
|
||||
class="hover:!no-underline"
|
||||
@click="
|
||||
handleAssistantAction({ action: 'approve', value: 'approve' })
|
||||
"
|
||||
@click="handleAssistantAction('approve', 'approve')"
|
||||
/>
|
||||
<Button
|
||||
:label="$t('CAPTAIN.RESPONSES.OPTIONS.EDIT_RESPONSE')"
|
||||
@@ -188,12 +199,7 @@ const handleDocumentableClick = () => {
|
||||
slate
|
||||
link
|
||||
class="hover:!no-underline"
|
||||
@click="
|
||||
handleAssistantAction({
|
||||
action: 'edit',
|
||||
value: 'edit',
|
||||
})
|
||||
"
|
||||
@click="handleAssistantAction('edit', 'edit')"
|
||||
/>
|
||||
<Button
|
||||
:label="$t('CAPTAIN.RESPONSES.OPTIONS.DELETE_RESPONSE')"
|
||||
@@ -202,78 +208,10 @@ const handleDocumentableClick = () => {
|
||||
ruby
|
||||
link
|
||||
class="hover:!no-underline"
|
||||
@click="
|
||||
handleAssistantAction({ action: 'delete', value: 'delete' })
|
||||
"
|
||||
@click="handleAssistantAction('delete', 'delete')"
|
||||
/>
|
||||
</div>
|
||||
</Policy>
|
||||
<div
|
||||
class="flex items-center gap-3"
|
||||
:class="{ 'justify-between w-full': !showActions }"
|
||||
>
|
||||
<div class="inline-flex items-center gap-3 min-w-0">
|
||||
<span
|
||||
v-if="status === 'approved'"
|
||||
class="text-sm shrink-0 truncate text-n-slate-11 inline-flex items-center gap-1"
|
||||
>
|
||||
<Icon icon="i-woot-captain" class="size-3.5" />
|
||||
{{ assistant?.name || '' }}
|
||||
</span>
|
||||
<div
|
||||
v-if="documentable"
|
||||
class="text-sm text-n-slate-11 grid grid-cols-[auto_1fr] items-center gap-1 min-w-0"
|
||||
>
|
||||
<Icon
|
||||
v-if="documentable.type === 'Captain::Document'"
|
||||
icon="i-ph-files-light"
|
||||
class="size-3.5"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="documentable.type === 'User'"
|
||||
icon="i-ph-user-circle-plus"
|
||||
class="size-3.5"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="documentable.type === 'Conversation'"
|
||||
icon="i-ph-chat-circle-dots"
|
||||
class="size-3.5"
|
||||
/>
|
||||
<span
|
||||
v-if="documentable.type === 'Captain::Document'"
|
||||
class="truncate"
|
||||
:title="documentable.name"
|
||||
>
|
||||
{{ documentable.name }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="documentable.type === 'User'"
|
||||
class="truncate"
|
||||
:title="documentable.available_name"
|
||||
>
|
||||
{{ documentable.available_name }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="documentable.type === 'Conversation'"
|
||||
class="hover:underline truncate cursor-pointer"
|
||||
role="button"
|
||||
@click="handleDocumentableClick"
|
||||
>
|
||||
{{
|
||||
t(`CAPTAIN.RESPONSES.DOCUMENTABLE.CONVERSATION`, {
|
||||
id: documentable.display_id,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="shrink-0 text-sm text-n-slate-11 line-clamp-1 inline-flex items-center gap-1"
|
||||
>
|
||||
<Icon icon="i-ph-calendar-dot" class="size-3.5" />
|
||||
{{ timestamp }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ defineExpose({ dialogRef });
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-3 min-h-48">
|
||||
<div v-else class="flex flex-col divide-y divide-n-weak min-h-48">
|
||||
<ResponseCard
|
||||
v-for="response in responses"
|
||||
:id="response.id"
|
||||
|
||||
+2
-1
@@ -60,7 +60,7 @@ const onClearFilters = () => {
|
||||
:show-backdrop="isApproved"
|
||||
>
|
||||
<template v-if="isApproved" #empty-state-item>
|
||||
<div class="grid grid-cols-1 gap-4 p-px overflow-hidden">
|
||||
<div class="grid grid-cols-1 divide-y divide-n-weak p-px overflow-hidden">
|
||||
<ResponseCard
|
||||
v-for="(response, index) in responsesList.slice(0, 5)"
|
||||
:id="response.id"
|
||||
@@ -68,6 +68,7 @@ const onClearFilters = () => {
|
||||
:question="replaceInstallationName(response.question)"
|
||||
:answer="replaceInstallationName(response.answer)"
|
||||
:status="response.status"
|
||||
:documentable="response.documentable"
|
||||
:assistant="response.assistant"
|
||||
:created-at="response.created_at"
|
||||
:updated-at="response.created_at"
|
||||
|
||||
+30
@@ -141,6 +141,11 @@ export const responsesList = [
|
||||
id: 87,
|
||||
question: 'Why is my Messenger in Chatwoot deactivated?',
|
||||
status: 'pending',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'Captain::Document',
|
||||
name: 'Document 1',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 1,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
@@ -157,6 +162,11 @@ export const responsesList = [
|
||||
created_at: 1736283340,
|
||||
id: 88,
|
||||
question: 'How do I integrate WhatsApp with Chatwoot?',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'User',
|
||||
available_name: 'John Doe',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 2,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
@@ -173,6 +183,11 @@ export const responsesList = [
|
||||
created_at: 1736283350,
|
||||
id: 89,
|
||||
question: 'How can I reset my password in Chatwoot?',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'Conversation',
|
||||
display_id: '12345',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 3,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
@@ -189,6 +204,11 @@ export const responsesList = [
|
||||
created_at: 1736283360,
|
||||
id: 90,
|
||||
question: 'How do I enable dark mode in Chatwoot?',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'Captain::Document',
|
||||
name: 'Document 1',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 4,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
@@ -205,6 +225,11 @@ export const responsesList = [
|
||||
created_at: 1736283370,
|
||||
id: 91,
|
||||
question: 'How do I add a new team member in Chatwoot?',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'User',
|
||||
available_name: 'John Doe',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 5,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
@@ -221,6 +246,11 @@ export const responsesList = [
|
||||
created_at: 1736283380,
|
||||
id: 92,
|
||||
question: 'What are campaigns in Chatwoot?',
|
||||
documentable: {
|
||||
id: 1,
|
||||
type: 'User',
|
||||
available_name: 'John Doe',
|
||||
},
|
||||
assistant: {
|
||||
account_id: 6,
|
||||
config: { product_name: 'Chatwoot' },
|
||||
|
||||
Reference in New Issue
Block a user