feat: Allow creating contact notes (#12494)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -555,10 +555,12 @@
|
|||||||
"WROTE": "wrote",
|
"WROTE": "wrote",
|
||||||
"YOU": "You",
|
"YOU": "You",
|
||||||
"SAVE": "Save note",
|
"SAVE": "Save note",
|
||||||
|
"ADD_NOTE": "Add contact note",
|
||||||
"EXPAND": "Expand",
|
"EXPAND": "Expand",
|
||||||
"COLLAPSE": "Collapse",
|
"COLLAPSE": "Collapse",
|
||||||
"NO_NOTES": "No notes, you can add notes from the contact details page.",
|
"NO_NOTES": "No notes, you can add notes from the contact details page.",
|
||||||
"EMPTY_STATE": "There are no notes associated to this contact. You can add a note by typing in the box above."
|
"EMPTY_STATE": "There are no notes associated to this contact. You can add a note by typing in the box above.",
|
||||||
|
"CONVERSATION_EMPTY_STATE": "There are no notes yet. Use the Add note button to create one."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMPTY_STATE": {
|
"EMPTY_STATE": {
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { watch, computed } from 'vue';
|
import { watch, computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||||
import ContactNoteItem from 'next/Contacts/ContactsSidebar/components/ContactNoteItem.vue';
|
|
||||||
import Spinner from 'next/spinner/Spinner.vue';
|
|
||||||
|
|
||||||
const { contactId } = defineProps({
|
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||||
contactId: { type: String, required: true },
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
|
import ContactNoteItem from 'next/Contacts/ContactsSidebar/components/ContactNoteItem.vue';
|
||||||
|
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
contactId: { type: [String, Number], required: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const currentUser = useMapGetter('getCurrentUser');
|
const currentUser = useMapGetter('getCurrentUser');
|
||||||
const uiFlags = useMapGetter('contactNotes/getUIFlags');
|
const uiFlags = useMapGetter('contactNotes/getUIFlags');
|
||||||
|
const notesByContact = useMapGetter('contactNotes/getAllNotesByContactId');
|
||||||
const isFetchingNotes = computed(() => uiFlags.value.isFetching);
|
const isFetchingNotes = computed(() => uiFlags.value.isFetching);
|
||||||
const notGetterFn = useMapGetter('contactNotes/getAllNotesByContactId');
|
const isCreatingNote = computed(() => uiFlags.value.isCreating);
|
||||||
const notes = computed(() => notGetterFn.value(contactId));
|
const contactId = computed(() => props.contactId);
|
||||||
|
const noteContent = ref('');
|
||||||
|
const shouldShowCreateModal = ref(false);
|
||||||
|
const notes = computed(() => {
|
||||||
|
if (!contactId.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return notesByContact.value(contactId.value) || [];
|
||||||
|
});
|
||||||
|
|
||||||
const getWrittenBy = ({ user } = {}) => {
|
const getWrittenBy = ({ user } = {}) => {
|
||||||
const currentUserId = currentUser.value?.id;
|
const currentUserId = currentUser.value?.id;
|
||||||
@@ -24,28 +37,130 @@ const getWrittenBy = ({ user } = {}) => {
|
|||||||
: user?.name || t('CONVERSATION.BOT');
|
: user?.name || t('CONVERSATION.BOT');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openCreateModal = () => {
|
||||||
|
if (!contactId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
noteContent.value = '';
|
||||||
|
shouldShowCreateModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeCreateModal = () => {
|
||||||
|
shouldShowCreateModal.value = false;
|
||||||
|
noteContent.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAdd = async () => {
|
||||||
|
if (!contactId.value || !noteContent.value || isCreatingNote.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.dispatch('contactNotes/create', {
|
||||||
|
content: noteContent.value,
|
||||||
|
contactId: contactId.value,
|
||||||
|
});
|
||||||
|
noteContent.value = '';
|
||||||
|
closeCreateModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = noteId => {
|
||||||
|
if (!contactId.value || !noteId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch('contactNotes/delete', {
|
||||||
|
noteId,
|
||||||
|
contactId: contactId.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyboardEvents = {
|
||||||
|
'$mod+Enter': {
|
||||||
|
action: onAdd,
|
||||||
|
allowOnFocusedInput: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
useKeyboardEvents(keyboardEvents);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => contactId,
|
contactId,
|
||||||
() => store.dispatch('contactNotes/get', { contactId }),
|
newContactId => {
|
||||||
|
closeCreateModal();
|
||||||
|
if (newContactId) {
|
||||||
|
store.dispatch('contactNotes/get', { contactId: newContactId });
|
||||||
|
}
|
||||||
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isFetchingNotes" class="p-8 grid place-content-center">
|
<div>
|
||||||
|
<div class="px-4 pt-3 pb-2">
|
||||||
|
<NextButton
|
||||||
|
ghost
|
||||||
|
xs
|
||||||
|
icon="i-lucide-plus"
|
||||||
|
:label="$t('CONTACTS_LAYOUT.SIDEBAR.NOTES.ADD_NOTE')"
|
||||||
|
:disabled="!contactId || isFetchingNotes"
|
||||||
|
@click="openCreateModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isFetchingNotes"
|
||||||
|
class="flex items-center justify-center py-8 text-n-slate-11"
|
||||||
|
>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!notes.length" class="p-8 grid place-content-center">
|
<div
|
||||||
<p class="text-center">{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.NO_NOTES') }}</p>
|
v-else-if="notes.length"
|
||||||
</div>
|
class="flex flex-col max-h-[300px] overflow-y-auto"
|
||||||
<div v-else class="max-h-[300px] overflow-scroll">
|
>
|
||||||
<ContactNoteItem
|
<ContactNoteItem
|
||||||
v-for="note in notes"
|
v-for="note in notes"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
class="p-4 last-of-type:border-b-0"
|
class="py-4 last-of-type:border-b-0 px-4"
|
||||||
:note="note"
|
:note="note"
|
||||||
collapsible
|
|
||||||
:written-by="getWrittenBy(note)"
|
:written-by="getWrittenBy(note)"
|
||||||
|
allow-delete
|
||||||
|
collapsible
|
||||||
|
@delete="onDelete"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-else class="px-6 py-6 text-sm leading-6 text-center text-n-slate-11">
|
||||||
|
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.CONVERSATION_EMPTY_STATE') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<woot-modal
|
||||||
|
v-model:show="shouldShowCreateModal"
|
||||||
|
:on-close="closeCreateModal"
|
||||||
|
:close-on-backdrop-click="false"
|
||||||
|
class="!items-start [&>div]:!top-12 [&>div]:sticky"
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-col gap-6 px-6 py-6">
|
||||||
|
<h3 class="text-lg font-semibold text-n-slate-12">
|
||||||
|
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.ADD_NOTE') }}
|
||||||
|
</h3>
|
||||||
|
<Editor
|
||||||
|
v-model="noteContent"
|
||||||
|
focus-on-mount
|
||||||
|
:placeholder="t('CONTACTS_LAYOUT.SIDEBAR.NOTES.PLACEHOLDER')"
|
||||||
|
class="[&>div]:!border-transparent [&>div]:px-4 [&>div]:py-4"
|
||||||
|
/>
|
||||||
|
<div class="flex items-center justify-end gap-3">
|
||||||
|
<NextButton
|
||||||
|
solid
|
||||||
|
blue
|
||||||
|
:label="t('CONTACTS_LAYOUT.SIDEBAR.NOTES.SAVE')"
|
||||||
|
:is-loading="isCreatingNote"
|
||||||
|
:disabled="!noteContent || isCreatingNote"
|
||||||
|
@click="onAdd"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</woot-modal>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user