Merge branch 'develop' of github.com:chatwoot/chatwoot into feat/ui-lib
This commit is contained in:
@@ -17,6 +17,7 @@ const props = defineProps({
|
||||
additionalAttributes: { type: Object, default: () => ({}) },
|
||||
phoneNumber: { type: String, default: '' },
|
||||
thumbnail: { type: String, default: '' },
|
||||
availabilityStatus: { type: String, default: null },
|
||||
isExpanded: { type: Boolean, default: false },
|
||||
isUpdating: { type: Boolean, default: false },
|
||||
});
|
||||
@@ -92,7 +93,13 @@ const onClickViewDetails = () => emit('showContact', props.id);
|
||||
<template>
|
||||
<CardLayout :key="id" layout="row">
|
||||
<div class="flex items-center justify-start flex-1 gap-4">
|
||||
<Avatar :name="name" :src="thumbnail" :size="48" rounded-full />
|
||||
<Avatar
|
||||
:name="name"
|
||||
:src="thumbnail"
|
||||
:size="48"
|
||||
:status="availabilityStatus"
|
||||
rounded-full
|
||||
/>
|
||||
<div class="flex flex-col gap-0.5 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1">
|
||||
<span class="text-base font-medium truncate text-n-slate-12">
|
||||
|
||||
+18
-39
@@ -7,42 +7,16 @@ import ContactMoreActions from './components/ContactMoreActions.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
||||
|
||||
defineProps({
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
searchValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headerTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activeSort: {
|
||||
type: String,
|
||||
default: 'last_activity_at',
|
||||
},
|
||||
activeOrdering: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isSegmentsView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasActiveFilters: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isLabelView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showSearch: { type: Boolean, default: true },
|
||||
searchValue: { type: String, default: '' },
|
||||
headerTitle: { type: String, required: true },
|
||||
buttonLabel: { type: String, default: '' },
|
||||
activeSort: { type: String, default: 'last_activity_at' },
|
||||
activeOrdering: { type: String, default: '' },
|
||||
isSegmentsView: { type: Boolean, default: false },
|
||||
hasActiveFilters: { type: Boolean, default: false },
|
||||
isLabelView: { type: Boolean, default: false },
|
||||
isActiveView: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -85,7 +59,7 @@ const emit = defineEmits([
|
||||
</Input>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="!isLabelView" class="relative">
|
||||
<div v-if="!isLabelView && !isActiveView" class="relative">
|
||||
<Button
|
||||
id="toggleContactsFilterButton"
|
||||
:icon="
|
||||
@@ -105,7 +79,12 @@ const emit = defineEmits([
|
||||
<slot name="filter" />
|
||||
</div>
|
||||
<Button
|
||||
v-if="hasActiveFilters && !isSegmentsView && !isLabelView"
|
||||
v-if="
|
||||
hasActiveFilters &&
|
||||
!isSegmentsView &&
|
||||
!isLabelView &&
|
||||
!isActiveView
|
||||
"
|
||||
icon="i-lucide-save"
|
||||
color="slate"
|
||||
size="sm"
|
||||
@@ -113,7 +92,7 @@ const emit = defineEmits([
|
||||
@click="emit('createSegment')"
|
||||
/>
|
||||
<Button
|
||||
v-if="isSegmentsView && !isLabelView"
|
||||
v-if="isSegmentsView && !isLabelView && !isActiveView"
|
||||
icon="i-lucide-trash"
|
||||
color="slate"
|
||||
size="sm"
|
||||
|
||||
+2
@@ -36,6 +36,7 @@ const props = defineProps({
|
||||
activeSegment: { type: Object, default: null },
|
||||
hasAppliedFilters: { type: Boolean, default: false },
|
||||
isLabelView: { type: Boolean, default: false },
|
||||
isActiveView: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -277,6 +278,7 @@ defineExpose({
|
||||
:header-title="headerTitle"
|
||||
:is-segments-view="hasActiveSegments"
|
||||
:is-label-view="isLabelView"
|
||||
:is-active-view="isActiveView"
|
||||
:has-active-filters="hasAppliedFilters"
|
||||
:button-label="t('CONTACTS_LAYOUT.HEADER.MESSAGE_BUTTON')"
|
||||
@search="emit('search', $event)"
|
||||
|
||||
@@ -6,7 +6,7 @@ import ContactListHeaderWrapper from 'dashboard/components-next/Contacts/Contact
|
||||
import ContactsActiveFiltersPreview from 'dashboard/components-next/Contacts/ContactsHeader/components/ContactsActiveFiltersPreview.vue';
|
||||
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
searchValue: { type: String, default: '' },
|
||||
headerTitle: { type: String, default: '' },
|
||||
showPaginationFooter: { type: Boolean, default: true },
|
||||
@@ -37,10 +37,23 @@ const isNotSegmentView = computed(() => {
|
||||
return route.name !== 'contacts_dashboard_segments_index';
|
||||
});
|
||||
|
||||
const isActiveView = computed(() => {
|
||||
return route.name === 'contacts_dashboard_active';
|
||||
});
|
||||
|
||||
const isLabelView = computed(
|
||||
() => route.name === 'contacts_dashboard_labels_index'
|
||||
);
|
||||
|
||||
const showActiveFiltersPreview = computed(() => {
|
||||
return (
|
||||
(props.hasAppliedFilters || !isNotSegmentView.value) &&
|
||||
!props.isFetchingList &&
|
||||
!isLabelView.value &&
|
||||
!isActiveView.value
|
||||
);
|
||||
});
|
||||
|
||||
const updateCurrentPage = page => {
|
||||
emit('update:currentPage', page);
|
||||
};
|
||||
@@ -57,7 +70,7 @@ const openFilter = () => {
|
||||
<div class="flex flex-col w-full h-full transition-all duration-300">
|
||||
<ContactListHeaderWrapper
|
||||
ref="contactListHeaderWrapper"
|
||||
:show-search="isNotSegmentView"
|
||||
:show-search="isNotSegmentView && !isActiveView"
|
||||
:search-value="searchValue"
|
||||
:active-sort="activeSort"
|
||||
:active-ordering="activeOrdering"
|
||||
@@ -66,6 +79,7 @@ const openFilter = () => {
|
||||
:segments-id="segmentsId"
|
||||
:has-applied-filters="hasAppliedFilters"
|
||||
:is-label-view="isLabelView"
|
||||
:is-active-view="isActiveView"
|
||||
@update:sort="emit('update:sort', $event)"
|
||||
@search="emit('search', $event)"
|
||||
@apply-filter="emit('applyFilter', $event)"
|
||||
@@ -74,11 +88,7 @@ const openFilter = () => {
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem]">
|
||||
<ContactsActiveFiltersPreview
|
||||
v-if="
|
||||
(hasAppliedFilters || !isNotSegmentView) &&
|
||||
!isFetchingList &&
|
||||
!isLabelView
|
||||
"
|
||||
v-if="showActiveFiltersPreview"
|
||||
:active-segment="activeSegment"
|
||||
@clear-filters="emit('clearFilters')"
|
||||
@open-filter="openFilter"
|
||||
|
||||
@@ -71,6 +71,7 @@ const toggleExpanded = id => {
|
||||
:thumbnail="contact.thumbnail"
|
||||
:phone-number="contact.phoneNumber"
|
||||
:additional-attributes="contact.additionalAttributes"
|
||||
:availability-status="contact.availabilityStatus"
|
||||
:is-expanded="expandedCardId === contact.id"
|
||||
:is-updating="isUpdating"
|
||||
@toggle="toggleExpanded(contact.id)"
|
||||
|
||||
@@ -4,38 +4,14 @@ import { computed, ref, watch, useSlots } from 'vue';
|
||||
import WootEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
focusOnMount: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
showCharacterCount: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: { type: String, default: '' },
|
||||
label: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
focusOnMount: { type: Boolean, default: false },
|
||||
maxLength: { type: Number, default: 200 },
|
||||
showCharacterCount: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' },
|
||||
messageType: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
@@ -43,6 +19,7 @@ const props = defineProps({
|
||||
},
|
||||
enableVariables: { type: Boolean, default: false },
|
||||
enableCannedResponses: { type: Boolean, default: true },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
@@ -120,6 +97,7 @@ watch(
|
||||
:disabled="disabled"
|
||||
:enable-variables="enableVariables"
|
||||
:enable-canned-responses="enableCannedResponses"
|
||||
:enabled-menu-options="enabledMenuOptions"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
|
||||
@@ -46,8 +46,6 @@ const getCurrentRoute = () => {
|
||||
const path = route.path;
|
||||
if (path.includes('/conversations')) return 'conversations';
|
||||
if (path.includes('/dashboard')) return 'dashboard';
|
||||
if (path.includes('/contacts')) return 'contacts';
|
||||
if (path.includes('/articles')) return 'articles';
|
||||
return 'dashboard';
|
||||
};
|
||||
|
||||
|
||||
@@ -109,49 +109,58 @@ const downloadAudio = async () => {
|
||||
</audio>
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
class="rounded-xl w-full gap-1 p-1.5 bg-n-alpha-white flex items-center border border-n-container shadow-[0px_2px_8px_0px_rgba(94,94,94,0.06)]"
|
||||
class="rounded-xl w-full gap-2 p-1.5 bg-n-alpha-white flex flex-col items-center border border-n-container shadow-[0px_2px_8px_0px_rgba(94,94,94,0.06)]"
|
||||
>
|
||||
<button class="p-0 border-0 size-8" @click="playOrPause">
|
||||
<Icon
|
||||
v-if="isPlaying"
|
||||
class="size-8"
|
||||
icon="i-teenyicons-pause-small-solid"
|
||||
/>
|
||||
<Icon v-else class="size-8" icon="i-teenyicons-play-small-solid" />
|
||||
</button>
|
||||
<div class="tabular-nums text-xs">
|
||||
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
|
||||
<div class="flex gap-1 w-full flex-1 items-center justify-start">
|
||||
<button class="p-0 border-0 size-8" @click="playOrPause">
|
||||
<Icon
|
||||
v-if="isPlaying"
|
||||
class="size-8"
|
||||
icon="i-teenyicons-pause-small-solid"
|
||||
/>
|
||||
<Icon v-else class="size-8" icon="i-teenyicons-play-small-solid" />
|
||||
</button>
|
||||
<div class="tabular-nums text-xs">
|
||||
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
|
||||
</div>
|
||||
<div class="flex-1 items-center flex px-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
class="w-full h-1 bg-n-slate-12/40 rounded-lg appearance-none cursor-pointer accent-current"
|
||||
@input="seek"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="border-0 w-10 h-6 grid place-content-center bg-n-alpha-2 hover:bg-alpha-3 rounded-2xl"
|
||||
@click="changePlaybackSpeed"
|
||||
>
|
||||
<span class="text-xs text-n-slate-11 font-medium">
|
||||
{{ playbackSpeedLabel }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="p-0 border-0 size-8 grid place-content-center"
|
||||
@click="toggleMute"
|
||||
>
|
||||
<Icon v-if="isMuted" class="size-4" icon="i-lucide-volume-off" />
|
||||
<Icon v-else class="size-4" icon="i-lucide-volume-2" />
|
||||
</button>
|
||||
<button
|
||||
class="p-0 border-0 size-8 grid place-content-center"
|
||||
@click="downloadAudio"
|
||||
>
|
||||
<Icon class="size-4" icon="i-lucide-download" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 items-center flex px-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
class="w-full h-1 bg-n-slate-12/40 rounded-lg appearance-none cursor-pointer accent-current"
|
||||
@input="seek"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="attachment.transcribedText"
|
||||
class="text-n-slate-12 p-3 text-sm bg-n-alpha-1 rounded-lg w-full break-words"
|
||||
>
|
||||
{{ attachment.transcribedText }}
|
||||
</div>
|
||||
<button
|
||||
class="border-0 w-10 h-6 grid place-content-center bg-n-alpha-2 hover:bg-alpha-3 rounded-2xl"
|
||||
@click="changePlaybackSpeed"
|
||||
>
|
||||
<span class="text-xs text-n-slate-11 font-medium">
|
||||
{{ playbackSpeedLabel }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="p-0 border-0 size-8 grid place-content-center"
|
||||
@click="toggleMute"
|
||||
>
|
||||
<Icon v-if="isMuted" class="size-4" icon="i-lucide-volume-off" />
|
||||
<Icon v-else class="size-4" icon="i-lucide-volume-2" />
|
||||
</button>
|
||||
<button
|
||||
class="p-0 border-0 size-8 grid place-content-center"
|
||||
@click="downloadAudio"
|
||||
>
|
||||
<Icon class="size-4" icon="i-lucide-download" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -235,6 +235,12 @@ const menuItems = computed(() => {
|
||||
),
|
||||
activeOn: ['contacts_dashboard_index', 'contacts_edit'],
|
||||
},
|
||||
{
|
||||
name: 'Active',
|
||||
label: t('SIDEBAR.ACTIVE'),
|
||||
to: accountScopedRoute('contacts_dashboard_active'),
|
||||
activeOn: ['contacts_dashboard_active'],
|
||||
},
|
||||
{
|
||||
name: 'Segments',
|
||||
icon: 'i-lucide-group',
|
||||
|
||||
Reference in New Issue
Block a user