feat: Contact note item component

This commit is contained in:
iamsivin
2024-11-21 15:25:06 +05:30
parent 2a724cad7c
commit b9b3c43d63
5 changed files with 180 additions and 42 deletions
@@ -3,21 +3,17 @@ import { reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useRoute } from 'vue-router';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import { dynamicTime } from 'shared/helpers/timeHelper';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import Editor from 'dashboard/components-next/Editor/Editor.vue';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import ContactNoteItem from './components/ContactNoteItem.vue';
const { t } = useI18n();
const store = useStore();
const route = useRoute();
const { formatMessage } = useMessageFormatter();
const state = reactive({
message: '',
});
@@ -88,45 +84,13 @@ useKeyboardEvents(keyboardEvents);
<Spinner />
</div>
<div v-else-if="notes.length > 0">
<div
<ContactNoteItem
v-for="note in notes"
:key="note.id"
class="flex flex-col gap-2 px-6 py-2 border-b border-n-strong group/note"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-1.5 py-2.5 min-w-0">
<Avatar
:name="note.user.name"
:src="note.user.thumbnail"
:size="16"
rounded-full
/>
<div class="min-w-0 truncate">
<span
class="inline-flex items-center gap-1 text-sm text-n-slate-11"
>
<span class="font-medium">{{ getWrittenBy(note) }}</span>
{{ $t('CONTACTS_LAYOUT.SIDEBAR.NOTES.WROTE') }}
<span class="font-medium">{{
dynamicTime(note.createdAt)
}}</span>
</span>
</div>
</div>
<Button
variant="faded"
color="ruby"
size="xs"
icon="i-lucide-trash"
class="opacity-0 group-hover/note:opacity-100"
@click="onDelete(note.id)"
/>
</div>
<p
v-dompurify-html="formatMessage(note.content || '')"
class="mb-0 prose-sm prose-p:mb-1 prose-p:mt-0 prose-ul:mb-1 prose-ul:mt-0 text-n-slate-12"
/>
</div>
:note="note"
:written-by="getWrittenBy(note)"
@delete="onDelete"
/>
</div>
<p v-else class="px-6 py-6 text-sm leading-6 text-center text-n-slate-11">
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.EMPTY_STATE') }}
@@ -0,0 +1,63 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { dynamicTime } from 'shared/helpers/timeHelper';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import Button from 'dashboard/components-next/button/Button.vue';
const props = defineProps({
note: {
type: Object,
required: true,
},
writtenBy: {
type: String,
required: true,
},
});
const emit = defineEmits(['delete']);
const { t } = useI18n();
const { formatMessage } = useMessageFormatter();
const handleDelete = () => {
emit('delete', props.note.id);
};
</script>
<template>
<div
class="flex flex-col gap-2 px-6 py-2 border-b border-n-strong group/note"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-1.5 py-2.5 min-w-0">
<Avatar
:name="note.user.name"
:src="note.user.thumbnail"
:size="16"
rounded-full
/>
<div class="min-w-0 truncate">
<span class="inline-flex items-center gap-1 text-sm text-n-slate-11">
<span class="font-medium">{{ writtenBy }}</span>
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.WROTE') }}
<span class="font-medium">{{ dynamicTime(note.createdAt) }}</span>
</span>
</div>
</div>
<Button
variant="faded"
color="ruby"
size="xs"
icon="i-lucide-trash"
class="opacity-0 group-hover/note:opacity-100"
@click="handleDelete"
/>
</div>
<p
v-dompurify-html="formatMessage(note.content || '')"
class="mb-0 prose-sm prose-p:mb-1 prose-p:mt-0 prose-ul:mb-1 prose-ul:mt-0 text-n-slate-12"
/>
</div>
</template>
@@ -0,0 +1,39 @@
<script setup>
import ContactNoteItem from '../ContactNoteItem.vue';
import notes from './fixtures';
const controls = {
writtenBy: {
type: 'text',
default: 'You',
},
};
// Example delete handler
const onDelete = noteId => {
console.log('Note deleted:', noteId);
};
</script>
<template>
<Story
title="Components/Contacts/ContactNoteItem"
:layout="{ type: 'grid', width: '600px' }"
>
<Variant title="Multiple Notes">
<div class="flex flex-col border rounded-lg border-n-strong">
<ContactNoteItem
v-for="note in notes"
:key="note.id"
:note="note"
:written-by="
note.id === notes[1].id
? controls.writtenBy.default
: note.user.name
"
@delete="onDelete"
/>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,69 @@
export default [
{
id: 12,
content:
'This tutorial will show you how to use Chatwoot and, hence, ensure you practice effective customer communication. We will explain in detail the following:\n\n* Step-by-step setup of your account, with illustrative screenshots.\n\n* An in-depth explanation of all the core features of Chatwoot.\n\n* Get your account up and running by the end of this tutorial.\n\n* Basic concepts of customer communication.',
accountId: null,
contactId: null,
user: {
id: 30,
account_id: 2,
availability_status: 'offline',
auto_offline: true,
confirmed: true,
email: 'bruce@paperlayer.test',
available_name: 'Bruce',
name: 'Bruce',
role: 'administrator',
thumbnail:
'https://sivin-tunnel.chatwoot.dev/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBJZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--515dbb35e9ba3c36d14f4c4b77220a675513c1fb/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2RTNKbGMybDZaVjkwYjE5bWFXeHNXd2RwQWZvdyIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--df796c2af3c0153e55236c2f3cf3a199ac2cb6f7/2.jpg',
custom_role_id: null,
},
createdAt: 1730786556,
updatedAt: 1730786556,
},
{
id: 10,
content:
'We discussed a couple of things:\n\n* Product offering and how it can be useful to talk with people.\n\n* Theyll reach out to us after an internal review.',
accountId: null,
contactId: null,
user: {
id: 1,
account_id: 2,
availability_status: 'online',
auto_offline: false,
confirmed: true,
email: 'hillary@chatwoot.com',
available_name: 'Hillary',
name: 'Hillary',
role: 'administrator',
thumbnail: '',
custom_role_id: null,
},
createdAt: 1730782566,
updatedAt: 1730782566,
},
{
id: 9,
content:
'We discussed a couple of things:\n\n* Product offering and how it can be useful to talk with people.\n\n* Theyll reach out to us after an internal review.',
accountId: null,
contactId: null,
user: {
id: 1,
account_id: 2,
availability_status: 'online',
auto_offline: false,
confirmed: true,
email: 'john@chatwoot.com',
available_name: 'John',
name: 'John',
role: 'administrator',
thumbnail: '',
custom_role_id: null,
},
createdAt: 1730782564,
updatedAt: 1730782564,
},
];
+3
View File
@@ -6,6 +6,8 @@ import { vResizeObserver } from '@vueuse/components';
import { directive as onClickaway } from 'vue3-click-away';
import FloatingVue from 'floating-vue';
import store from 'dashboard/store';
import VueDOMPurifyHTML from 'vue-dompurify-html';
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer.js';
const i18n = createI18n({
legacy: false, // https://github.com/intlify/vue-i18n/issues/1902
@@ -23,4 +25,5 @@ export const setupVue3 = defineSetupVue3(({ app }) => {
arrowOverflow: false,
disposeTimeout: 5000000,
});
app.use(VueDOMPurifyHTML, domPurifyConfig);
});