feat: Add contact note item component (#10479)
This commit is contained in:
@@ -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* They’ll 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* They’ll 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,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -440,6 +440,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"SIDEBAR": {
|
||||||
|
"NOTES": {
|
||||||
|
"PLACEHOLDER": "Add a note",
|
||||||
|
"WROTE": "wrote",
|
||||||
|
"YOU": "You",
|
||||||
|
"SAVE": "Save note",
|
||||||
|
"EMPTY_STATE": "There are no notes associated to this contact. You can add a note by typing in the box above."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import i18nMessages from 'dashboard/i18n';
|
|||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
import { vResizeObserver } from '@vueuse/components';
|
import { vResizeObserver } from '@vueuse/components';
|
||||||
import store from 'dashboard/store';
|
import store from 'dashboard/store';
|
||||||
|
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||||
|
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer.js';
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false, // https://github.com/intlify/vue-i18n/issues/1902
|
legacy: false, // https://github.com/intlify/vue-i18n/issues/1902
|
||||||
@@ -15,4 +17,5 @@ export const setupVue3 = defineSetupVue3(({ app }) => {
|
|||||||
app.use(store);
|
app.use(store);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
app.directive('resize', vResizeObserver);
|
app.directive('resize', vResizeObserver);
|
||||||
|
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user