Message: Send/reply form refactoring - refs BT#21648

pull/5544/head
Angel Fernando Quiroz Campos 1 year ago
parent ef9548abd3
commit 48cae77b15
  1. 185
      assets/vue/components/message/Form.vue
  2. 127
      assets/vue/views/message/MessageCreate.vue
  3. 198
      assets/vue/views/message/MessageReply.vue

@ -3,11 +3,43 @@
<div class="col-span-2"> <div class="col-span-2">
<BaseInputText <BaseInputText
id="item_title" id="item_title"
v-model="item.title" v-model="messagePayload.title"
:label="t('Title')" :label="t('Title')"
/> />
<BaseAutocomplete
id="to"
v-model="usersTo"
:label="t('To')"
:search="asyncFind"
is-multiple
/>
<BaseAutocomplete
id="cc"
v-model="usersCc"
:label="t('Cc')"
:search="asyncFind"
is-multiple
/>
<BaseTinyEditor
v-model="messagePayload.content"
:full-page="false"
editor-id="message"
required
/>
<slot></slot> <slot></slot>
<BaseButton
:disabled="!canSubmitMessage"
:label="t('Send')"
icon="plus"
type="primary"
class="mb-2"
@click="onSubmit"
/>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
@ -17,14 +49,14 @@
</p> </p>
<ul <ul
v-if="attachments && attachments.length > 0" v-if="resourceFileList && resourceFileList.length > 0"
class="space-y-2" class="space-y-2"
> >
<li <li
v-for="(attachment, index) in attachments" v-for="(resourceFile, index) in resourceFileList"
:key="index" :key="index"
class="text-body-2" class="text-body-2"
v-text="attachment.originalName" v-text="resourceFile.originalName"
/> />
</ul> </ul>
@ -40,33 +72,146 @@
<script setup> <script setup>
import BaseInputText from "../basecomponents/BaseInputText.vue" import BaseInputText from "../basecomponents/BaseInputText.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { computed } from "vue" import { computed, ref, watch } from "vue"
import BaseIcon from "../basecomponents/BaseIcon.vue" import BaseIcon from "../basecomponents/BaseIcon.vue"
import BaseUploader from "../basecomponents/BaseUploader.vue" import BaseUploader from "../basecomponents/BaseUploader.vue"
import resourceFileService from "../../services/resourceFileService" import resourceFileService from "../../services/resourceFileService"
import BaseAutocomplete from "../basecomponents/BaseAutocomplete.vue"
const attachments = defineModel("attachments", { import userService from "../../services/userService"
type: Array, import { MESSAGE_TYPE_INBOX } from "./constants"
}) import BaseButton from "../basecomponents/BaseButton.vue"
import { useSecurityStore } from "../../store/securityStore"
import { MESSAGE_REL_USER_TYPE_CC, MESSAGE_REL_USER_TYPE_TO } from "../../constants/entity/messagereluser"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
const props = defineProps({ const props = defineProps({
values: { title: {
type: Object, type: String,
required: true, required: false,
default: "",
}, },
errors: { receiversTo: {
type: Object, type: Array,
default: () => {}, required: false,
default: () => [],
},
receiversCc: {
type: Array,
required: false,
default: () => [],
}, },
initialValues: { content: {
type: Object, type: String,
default: () => {}, required: false,
default: "",
},
attachments: {
type: Array,
required: false,
default: () => [],
},
msgType: {
type: Number,
required: false,
default: MESSAGE_TYPE_INBOX,
}, },
}) })
const emit = defineEmits(["submit"])
const { t } = useI18n() const { t } = useI18n()
const item = computed(() => props.initialValues || props.values) const securityStore = useSecurityStore()
const messagePayload = ref({
sender: securityStore.user["@id"],
msgType: MESSAGE_TYPE_INBOX,
title: "",
content: "",
receivers: [],
attachments: [],
})
const usersTo = ref([])
const usersCc = ref([])
const resourceFileList = ref([])
watch(
() => props.title,
(newTitle) => (messagePayload.value.title = newTitle),
)
watch(
() => props.content,
(newContent) => (messagePayload.value.content = newContent),
)
watch(
() => props.msgType,
(newMsgType) => (messagePayload.value.msgType = newMsgType),
)
watch(
() => props.receiversTo,
(newReceiversTo) => {
usersTo.value.push(
...newReceiversTo.map((messageRelUser) => ({
name: messageRelUser.fullName,
value: messageRelUser["@id"],
})),
)
},
)
watch(
() => props.receiversCc,
(newReceiversCc) => {
usersTo.value.push(
...newReceiversCc.map((messageRelUser) => ({
name: messageRelUser.fullName,
value: messageRelUser["@id"],
})),
)
},
)
async function asyncFind(query) {
const { items } = await userService.findBySearchTerm(query)
return items.map((member) => ({
name: member.fullName,
value: member["@id"],
}))
}
function onUploadSuccess({ response }) {
resourceFileList.value.push(response)
}
const canSubmitMessage = computed(() => {
return (
(usersTo.value.length > 0 || usersCc.value.length > 0) &&
messagePayload.value.title.trim() !== "" &&
messagePayload.value.content.trim() !== ""
)
})
function onSubmit() {
messagePayload.value.receivers = [
...usersTo.value.map((userTo) => ({
receiver: userTo.value,
receiverType: MESSAGE_REL_USER_TYPE_TO,
})),
...usersCc.value.map((userCc) => ({
receiver: userCc.value,
receiverType: MESSAGE_REL_USER_TYPE_CC,
})),
]
messagePayload.value.attachments = resourceFileList.value.map((resourceFile) => ({
resourceFileToAttach: resourceFile["@id"],
}))
const onUploadSuccess = ({ response }) => attachments.value.push(response) emit("submit", messagePayload.value)
}
</script> </script>

@ -1,73 +1,24 @@
<template> <template>
<MessageForm <MessageForm
v-model:attachments="attachments" :content="message.content"
:values="item" :receivers-to="message.receiversTo"
> :title="message.title"
<div @submit="onSubmit"
v-if="sendToUser"
class="field space-x-4"
>
<span v-t="'To'" />
<MessageCommunicationParty
:username="sendToUser.username"
:full-name="sendToUser.fullName"
:profile-image-url="sendToUser.illustrationUrl"
/> />
</div>
<BaseAutocomplete
v-else
id="to"
v-model="usersTo"
:label="t('To')"
:search="asyncFind"
is-multiple
/>
<BaseAutocomplete
v-if="!sendToUser"
id="cc"
v-model="usersCc"
:label="t('Cc')"
:search="asyncFind"
is-multiple
/>
<BaseTinyEditor
v-model="item.content"
editor-id="message"
required
/>
<BaseButton
:label="t('Send')"
:disabled="!canSubmitMessage"
icon="plus"
type="primary"
class="mb-2"
@click="onSubmit"
/>
</MessageForm>
<Loading :visible="isLoading || isLoadingUser" /> <Loading :visible="isLoading || isLoadingUser" />
</template> </template>
<script setup> <script setup>
import MessageForm from "../../components/message/Form.vue" import MessageForm from "../../components/message/Form.vue"
import Loading from "../../components/Loading.vue" import Loading from "../../components/Loading.vue"
import { computed, onMounted, ref } from "vue" import { onMounted, ref } from "vue"
import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { MESSAGE_TYPE_INBOX } from "../../components/message/constants"
import userService from "../../services/userService" import userService from "../../services/userService"
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { capitalize } from "lodash" import { capitalize } from "lodash"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import { messageService } from "../../services/message" import { messageService } from "../../services/message"
import MessageCommunicationParty from "./MessageCommunicationParty.vue"
import { MESSAGE_REL_USER_TYPE_CC, MESSAGE_REL_USER_TYPE_TO } from "../../constants/entity/messagereluser"
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
const router = useRouter() const router = useRouter()
@ -76,67 +27,19 @@ const { t } = useI18n()
const notification = useNotification() const notification = useNotification()
const asyncFind = async (query) => { const message = ref({
const { items } = await userService.findBySearchTerm(query)
return items.map((member) => ({
name: member.fullName,
value: member["@id"],
}))
}
const item = ref({
sender: securityStore.user["@id"],
receivers: [],
msgType: MESSAGE_TYPE_INBOX,
title: "", title: "",
content: "", content: "",
attachments: [], receiversTo: [],
})
const attachments = ref([])
const usersTo = ref([])
const usersCc = ref([])
const receiversTo = computed(() =>
usersTo.value.map((userTo) => ({
receiver: userTo.value,
receiverType: MESSAGE_REL_USER_TYPE_TO,
})),
)
const receiversCc = computed(() =>
usersCc.value.map((userCc) => ({
receiver: userCc.value,
receiverType: MESSAGE_REL_USER_TYPE_CC,
})),
)
const canSubmitMessage = computed(() => {
return (
(usersTo.value.length > 0 || usersCc.value.length > 0) &&
item.value.title.trim() !== "" &&
item.value.content.trim() !== ""
)
}) })
const isLoading = ref(false) const isLoading = ref(false)
const onSubmit = async () => { const onSubmit = async (messageToSend) => {
if (!canSubmitMessage.value) {
return
}
item.value.attachments = attachments.value.map((attachment) => ({
resourceFileToAttach: attachment["@id"],
}))
item.value.receivers = [...receiversTo.value, ...receiversCc.value]
isLoading.value = true isLoading.value = true
try { try {
await messageService.create(item.value) await messageService.create(messageToSend)
} catch (error) { } catch (error) {
notification.showErrorNotification(error) notification.showErrorNotification(error)
} finally { } finally {
@ -150,7 +53,6 @@ const onSubmit = async () => {
} }
const isLoadingUser = ref(false) const isLoadingUser = ref(false)
const sendToUser = ref()
onMounted(async () => { onMounted(async () => {
if (route.query.send_to_user) { if (route.query.send_to_user) {
@ -158,18 +60,13 @@ onMounted(async () => {
try { try {
let user = await userService.findById(route.query.send_to_user) let user = await userService.findById(route.query.send_to_user)
sendToUser.value = user message.value.receiversTo = [user]
usersTo.value.push({
name: user.fullName,
value: user["@id"],
})
if (route.query.prefill) { if (route.query.prefill) {
const prefill = capitalize(route.query.prefill) const prefill = capitalize(route.query.prefill)
item.value.title = t(prefill + "Title") message.value.title = t(prefill + "Title")
item.value.content = t(prefill + "Content", [ message.value.content = t(prefill + "Content", [
user.firstname, user.firstname,
securityStore.user.firstname, securityStore.user.firstname,
securityStore.user.firstname, securityStore.user.firstname,

@ -1,69 +1,29 @@
<template> <template>
<MessageForm <MessageForm
ref="createForm" v-if="replyMessage"
:errors="violations" :title="replyMessage.title"
:values="item" :content="replyMessage.content"
> :receivers-to="replyMessage.receiversTo"
<div :receivers-cc="replyMessage.receiversCc"
v-if="item.originalSender" @submit="onReplyMessageForm"
class="field space-x-4"
>
<span v-t="'To'" />
<MessageCommunicationParty
:username="item.originalSender.username"
:full-name="item.originalSender.fullName"
:profile-image-url="item.originalSender.illustrationUrl"
/> />
</div>
<div
v-if="item.receiversCc"
class="field space-x-4"
>
<span v-t="'Cc'" />
<MessageCommunicationParty
v-for="receiver in item.receiversCc"
:key="receiver.receiver.id"
:username="receiver.receiver.username"
:full-name="receiver.receiver.fullName"
:profile-image-url="receiver.receiver.illustrationUrl"
/>
</div>
<BaseTinyEditor
v-model="item.content"
:full-page="false"
editor-id="message"
required
/>
<BaseButton
:label="t('Send')"
icon="plus"
type="primary"
@click="onReplyMessageForm"
/>
</MessageForm>
<Loading :visible="isLoading" /> <Loading :visible="isLoading" />
</template> </template>
<script setup> <script setup>
import { useStore } from "vuex" import { useStore } from "vuex"
import { computed, onMounted, ref } from "vue" import { ref } from "vue"
import MessageForm from "../../components/message/Form.vue" import MessageForm from "../../components/message/Form.vue"
import Loading from "../../components/Loading.vue" import Loading from "../../components/Loading.vue"
import isEmpty from "lodash/isEmpty" import isEmpty from "lodash/isEmpty"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { MESSAGE_TYPE_INBOX } from "../../components/message/constants" import { MESSAGE_TYPE_INBOX } from "../../components/message/constants"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { formatDateTimeFromISO } from "../../utils/dates" import { useFormatDate } from "../../composables/formatDate"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue" import userService from "../../services/userService"
import MessageCommunicationParty from "./MessageCommunicationParty.vue"
const item = ref({})
const store = useStore() const store = useStore()
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
const route = useRoute() const route = useRoute()
@ -72,106 +32,84 @@ const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
const notification = useNotification() const notification = useNotification()
const { abbreviatedDatetime } = useFormatDate()
let id = isEmpty(route.params.id) ? route.query.id : route.params.id let id = isEmpty(route.params.id) ? route.query.id : route.params.id
let replyAll = "1" === route.query.all let replyAll = "1" === route.query.all
onMounted(async () => { const originalMessage = ref(null)
const response = await store.dispatch("message/load", id) const originalSenderInfo = ref(null)
item.value = await response
const originalUserInfo = await store.dispatch("user/load", "/api/users/" + item.value.sender.id)
const originalSenderName = originalUserInfo.fullName
const originalSenderEmail = originalUserInfo.email
const formattedDate = formatDateTimeFromISO(item.value.sendDate)
const translatedHeader = t("Email reply header", [
formattedDate,
originalSenderName,
`<a href="mailto:${originalSenderEmail}">${originalSenderEmail}</a>`,
])
delete item.value["@id"]
delete item.value["id"]
delete item.value["firstReceiver"]
//delete item.value['receivers'];
delete item.value["sendDate"]
item.value["originalSender"] = item.value["sender"] function filterReceiver(messageRelUser) {
// New sender.
item.value["sender"] = securityStore.user["@id"]
// Set new receivers, will be loaded by onSendMessageForm()
if (replyAll) {
item.value.receiversTo.forEach((user) => {
// Dont' add original sender. // Dont' add original sender.
if (item.value["originalSender"]["@id"] === user.receiver["@id"]) { if (originalMessage.value.sender["@id"] === messageRelUser.receiver["@id"]) {
return return false
}
// Dont' add the current user.
if (securityStore.user["@id"] === user.receiver["@id"]) {
return
} }
item.value.receiversCc.push(user)
})
// Check that the original sender is not already in the Cc. // Don't add the current user.
item.value.receiversCc.forEach(function (user, index, obj) { if (securityStore.user["@id"] === messageRelUser.receiver["@id"]) {
if (item.value["originalSender"]["@id"] === user.receiver["@id"]) { return false
obj.splice(index, 1)
} }
})
/*item.value.receiversTo.forEach(function (user, index, obj) { return true
if (securityStore.user['@id'] === user.receiver['@id']) {
obj.splice(index, 1);
}
});*/
} else {
item.value["receivers"] = []
item.value["receiversTo"] = null
item.value["receiversCc"] = null
item.value["receivers"][0] = item.value["originalSender"]
} }
/*console.log('-----------------------'); const replyMessage = ref({})
console.log(item.value.receiversCc);
if (item.value.receiversCc) {
item.value.receiversCc.forEach(user => {
console.log(user);
// Send to inbox
usersCc.value.push(user.receiver);
});
}*/
// Set reply content.
item.value.title = t("Re:") + " " + item.value.title
item.value.content = `<br /><br /><hr /><blockquote>${translatedHeader}<hr />${item.value.content}</blockquote>`
})
const isLoading = computed(() => store.state.message.isLoading) store.dispatch("message/load", id).then((messagePayload) => {
const violations = computed(() => store.state.message.violations) originalMessage.value = messagePayload
const createForm = ref() userService
.find(messagePayload.sender["@id"])
.then((senderPayload) => {
originalSenderInfo.value = senderPayload
const onReplyMessageForm = async () => { setReplyMessage()
let users = []
users.push({
receiver: createForm.value.v$.item.$model.originalSender["@id"],
receiverType: 1,
}) })
if (createForm.value.v$.item.$model.receiversCc) { .finally(() => (isLoading.value = false))
createForm.value.v$.item.$model.receiversCc.forEach((user) => {
// Send to inbox
users.push({ receiver: user.receiver["@id"], receiverType: 2 })
}) })
function setReplyMessage() {
const sendDate = abbreviatedDatetime(originalMessage.value.sendDate)
const senderFullName = originalSenderInfo.value.fullName
const sendeEmail = originalSenderInfo.value.email
const replyHeader = t("Email reply header", [
sendDate,
senderFullName,
`<a href="mailto:${sendeEmail}">${sendeEmail}</a>`,
])
replyMessage.value = {
receiversTo: [],
receiversCc: [],
msgType: MESSAGE_TYPE_INBOX,
title: t("Re:") + " " + originalMessage.value.title,
content: `<br /><br /><hr /><blockquote>${replyHeader}<hr />${originalMessage.value.content}</blockquote>`,
attachments: [],
} }
createForm.value.v$.item.$model.sender = "/api/users/" + securityStore.user.id
createForm.value.v$.item.$model.receiversTo = null if (replyAll) {
createForm.value.v$.item.$model.receiversCc = null replyMessage.value.receiversTo = originalMessage.value.receiversTo
createForm.value.v$.item.$model.receivers = users .filter(filterReceiver)
createForm.value.v$.item.$model.msgType = MESSAGE_TYPE_INBOX .map((messageRelUser) => messageRelUser.receiver)
replyMessage.value.receiversCc = originalMessage.value.receiversCc
.filter(filterReceiver)
.map((messageRelUser) => messageRelUser.receiver)
}
replyMessage.value.receiversTo.push(originalMessage.value.sender)
}
const isLoading = ref(true)
async function onReplyMessageForm(messageToSend) {
isLoading.value = true
try { try {
await store.dispatch("message/create", createForm.value.v$.item.$model) await store.dispatch("message/create", messageToSend)
notification.showSuccessNotification("Message sent") notification.showSuccessNotification("Message sent")
@ -180,6 +118,8 @@ const onReplyMessageForm = async () => {
}) })
} catch (e) { } catch (e) {
notification.showErrorNotification(e) notification.showErrorNotification(e)
} finally {
isLoading.value = false
} }
} }
</script> </script>

Loading…
Cancel
Save