Chamilo is a learning management system focused on ease of use and accessibility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
chamilo-lms/assets/vue/views/message/MessageList.vue

629 lines
16 KiB

<template>
<div class="message-list flex flex-col">
<SectionHeader :title="title">
<BaseButton
icon="email-plus"
only-icon
type="black"
@click="goToCompose"
/>
<BaseButton
:disabled="isLoading"
icon="refresh"
only-icon
type="black"
@click="refreshMessages"
/>
<BaseButton
:disabled="0 === selectedItems.length || isLoading"
icon="delete"
only-icon
type="black"
@click="showDlgConfirmDeleteMultiple"
/>
<BaseButton
:disabled="0 === selectedItems.length || isLoading"
icon="multiple-marked"
only-icon
popup-identifier="course-messages-list-tmenu"
type="black"
@click="mToggleMessagesList"
/>
<BaseMenu
id="course-messages-list-tmenu"
ref="mMessageList"
:model="mItemsMarkAs"
/>
</SectionHeader>
<div class="message-list__actions">
<BaseButton
:label="t('Inbox')"
icon="inbox"
type="black"
@click="showInbox"
class="w-full md:w-auto"
/>
<BaseButton
:label="t('Unread')"
icon="email-unread"
type="black"
@click="showUnread"
class="w-full md:w-auto"
/>
<BaseButton
:label="t('Sent')"
icon="sent"
type="black"
@click="showSent"
class="w-full md:w-auto"
/>
<BaseButton
v-for="tag in tags"
:key="tag.id"
:label="tag.tag"
icon="tag-outline"
type="black"
@click="showInboxByTag(tag)"
class="w-full md:w-auto"
/>
</div>
<div class="hidden md:block overflow-x-auto">
<DataTable
ref="dtMessages"
v-model:selection="selectedItems"
:loading="isLoading"
:row-class="rowClass"
:rows="initialRowsPerPage"
:rows-per-page-options="[10, 20, 50]"
:total-records="totalItems"
:value="items"
current-page-report-template="{first} to {last} of {totalRecords}"
data-key="@id"
lazy
paginator
paginator-template="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
responsive-layout="scroll"
sort-field="sendDate"
:sort-order="-1"
striped-rows
@page="onPage"
@sort="sortingChanged"
class="w-full table-auto"
>
<template #header>
<form
class="message-list__searcher-container"
@submit.prevent="onSearch"
>
<InputGroup>
<InputText
v-model="searchText"
:placeholder="t('Search')"
type="text"
/>
<BaseButton
icon="search"
type="primary"
is-submit
/>
<BaseButton
icon="close"
type="primary"
@click="onResetSearch"
/>
</InputGroup>
</form>
</template>
<Column selection-mode="multiple" />
<Column :header="showingInbox ? t('From') : t('To')">
<template #body="slotProps">
<BaseAvatarList
v-if="showingInbox && slotProps.data.sender"
:users="[slotProps.data.sender]"
/>
<div
v-else-if="showingInbox && !slotProps.data.sender"
v-text="t('No sender')"
/>
<BaseAvatarList
v-else-if="!showingInbox"
:users="mapReceiverMixToUsers(slotProps.data)"
/>
</template>
</Column>
<Column
:header="t('Title')"
:sortable="true"
field="title"
>
<template #body="slotProps">
<BaseAppLink
class="text-primary"
:to="{ name: 'MessageShow', query: { id: slotProps.data['@id'] } }"
>
{{ slotProps.data.title }}
</BaseAppLink>
<BaseTag
v-for="tag in findMyReceiver(slotProps.data)?.tags"
:key="tag.id"
:label="tag.tag"
type="info"
/>
</template>
</Column>
<Column
:header="t('Send date')"
:sortable="true"
field="sendDate"
class="truncate w-24 md:w-auto"
>
<template #body="slotProps">
{{ abbreviatedDatetime(slotProps.data.sendDate) }}
</template>
</Column>
<Column :header="t('Actions')">
<template #body="slotProps">
<BaseButton
icon="delete"
size="small"
type="danger"
@click="showDlgConfirmDeleteSingle(slotProps)"
/>
</template>
</Column>
</DataTable>
</div>
<!-- List for small screens with pagination -->
<div class="block md:hidden">
<ul class="space-y-4">
<li
v-for="item in paginatedItems"
:key="item.id"
class="bg-white shadow-md rounded-lg p-4"
>
<div class="flex items-center space-x-4">
<BaseAvatarList
v-if="showingInbox && item.sender"
:users="[item.sender]"
/>
<div
v-else-if="showingInbox && !item.sender"
class="text-sm text-gray-600"
v-text="t('No sender')"
/>
<BaseAvatarList
v-else
:users="mapReceiverMixToUsers(item)"
/>
<div class="flex-1">
<div v-if="showingInbox && item.sender" class="font-bold text-lg">
{{ item.sender.name }}
</div>
<div v-if="showingInbox && item.sender" class="text-sm text-gray-600">
{{ item.sender.email }}
</div>
</div>
</div>
<div class="mt-4">
<div class="text-sm font-bold">{{ t('Title') }}:</div>
<BaseAppLink
class="text-base text-blue-600"
:to="{ name: 'MessageShow', query: { id: item['@id'] } }"
>
{{ item.title }}
</BaseAppLink>
</div>
<div v-if="findMyReceiver(item)?.tags.length" class="mt-2">
<div class="text-sm font-bold">{{ t('Tags') }}:</div>
<div>
<BaseTag
v-for="tag in findMyReceiver(item)?.tags"
:key="tag.id"
:label="tag.tag"
type="info"
/>
</div>
</div>
<div class="mt-2">
<div class="text-sm font-bold">{{ t('Send date') }}:</div>
<div class="text-base text-gray-500">{{ abbreviatedDatetime(item.sendDate) }}</div>
</div>
<div class="mt-4 flex space-x-2">
<BaseButton
icon="delete"
size="small"
type="danger"
@click="showDlgConfirmDeleteSingle(item)"
/>
</div>
</li>
</ul>
<div class="flex justify-between items-center mt-4">
<BaseButton
:disabled="isPrevDisabled"
@click="prevPage"
icon="back"
type="black"
/>
<span>{{ t('Page') }} {{ currentPage }} {{ t('of') }} {{ totalPages }} ({{ totalItems }} {{ t('messages') }})</span>
<BaseButton
:disabled="isNextDisabled"
@click="nextPage"
icon="next"
type="black"
/>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from "vue"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
import { useRoute, useRouter } from "vue-router"
import { useFormatDate } from "../../composables/formatDate"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import BaseMenu from "../../components/basecomponents/BaseMenu.vue"
import BaseAvatarList from "../../components/basecomponents/BaseAvatarList.vue"
import BaseTag from "../../components/basecomponents/BaseTag.vue"
import DataTable from "primevue/datatable"
import Column from "primevue/column"
import { useConfirm } from "primevue/useconfirm"
import { useQuery } from "@vue/apollo-composable"
import { MESSAGE_STATUS_DELETED, MESSAGE_TYPE_INBOX, MESSAGE_TYPE_SENDER } from "../../constants/entity/message"
import { GET_USER_MESSAGE_TAGS } from "../../graphql/queries/MessageTag"
import { useNotification } from "../../composables/notification"
import { useMessageRelUserStore } from "../../store/messageRelUserStore"
import { useSecurityStore } from "../../store/securityStore"
import SectionHeader from "../../components/layout/SectionHeader.vue"
import InputGroup from "primevue/inputgroup"
import InputText from "primevue/inputtext"
import BaseAppLink from "../../components/basecomponents/BaseAppLink.vue"
import messageRelUserService from "../../services/messagereluser"
import { useMessageReceiverFormatter } from "../../composables/message/messageFormatter"
const route = useRoute()
const router = useRouter()
const store = useStore()
const securityStore = useSecurityStore()
const { t } = useI18n()
const confirm = useConfirm()
const notification = useNotification()
const messageRelUserStore = useMessageRelUserStore()
const { abbreviatedDatetime } = useFormatDate()
const { mapReceiverMixToUsers } = useMessageReceiverFormatter()
const mItemsMarkAs = ref([
{
label: t("As read"),
command: () => {
const promises = selectedItems.value.map((message) => {
const myReceiver = findMyReceiver(message)
if (!myReceiver) {
return undefined
}
myReceiver.read = true
return messageRelUserService.update(myReceiver["@id"], myReceiver)
})
Promise.all(promises)
.then(() => messageRelUserStore.findUnreadCount())
.catch((e) => notification.showErrorNotification(e))
.finally(() => (selectedItems.value = []))
},
},
{
label: t("As unread"),
command: async () => {
const promises = selectedItems.value.map((message) => {
const myReceiver = findMyReceiver(message)
if (!myReceiver) {
return undefined
}
myReceiver.read = false
return messageRelUserService.update(myReceiver["@id"], myReceiver)
})
Promise.all(promises)
.then(() => messageRelUserStore.findUnreadCount())
.catch((e) => notification.showErrorNotification(e))
.finally(() => (selectedItems.value = []))
},
},
])
const mMessageList = ref(null)
const mToggleMessagesList = (event) => mMessageList.value.toggle(event)
const dtMessages = ref(null)
const initialRowsPerPage = 10
const goToCompose = () => {
router.push({
name: "MessageCreate",
query: route.query,
})
}
const { result: messageTagsResult } = useQuery(
GET_USER_MESSAGE_TAGS,
{ user: securityStore.user["@id"] },
{ fetchPolicy: "cache-and-network" },
)
const tags = computed(() => messageTagsResult.value?.messageTags?.edges.map(({ node }) => node) ?? [])
const items = computed(() => store.getters["message/getRecents"])
const isLoading = computed(() => store.getters["message/isLoading"])
const totalItems = computed(() => store.getters["message/getTotalItems"])
const title = ref(null)
const selectedTag = ref(null)
const searchText = ref("")
const selectedItems = ref([])
const rowClass = (data) => {
const myReceiver = findMyReceiver(data)
if (!myReceiver) {
return []
}
return [{ "font-semibold": !myReceiver.read }]
}
let fetchPayload = {}
const rows = ref(10)
const currentPage = ref(1)
const totalPages = computed(() => {
return Math.ceil(totalItems.value / rows.value)
})
const paginatedItems = computed(() => {
if (!items.value.length) {
return []
}
return items.value
})
function nextPage() {
if (currentPage.value < totalPages.value) {
currentPage.value++
fetchPayload.page = currentPage.value
fetchPayload.itemsPerPage = rows.value
loadMessages(false)
}
}
function prevPage() {
if (currentPage.value > 1) {
currentPage.value--
fetchPayload.page = currentPage.value
fetchPayload.itemsPerPage = rows.value
loadMessages(false)
}
}
const isPrevDisabled = computed(() => currentPage.value === 1)
const isNextDisabled = computed(() => currentPage.value === totalPages.value)
function loadMessages(reset = true) {
if (reset) {
store.dispatch("message/resetList")
dtMessages.value.resetPage()
}
fetchPayload.msgType = MESSAGE_TYPE_INBOX
fetchPayload.status = 0
if (selectedTag.value) {
fetchPayload["receivers.tags.tag"] = selectedTag.value.tag
}
if (showingInbox.value) {
fetchPayload["receivers.receiver"] = securityStore.user["@id"]
} else {
fetchPayload.sender = securityStore.user["@id"]
}
if (searchText.value) {
fetchPayload.search = searchText.value
}
store.dispatch("message/fetchAll", fetchPayload)
}
const showingInbox = ref(false)
function showInbox() {
showingInbox.value = true
title.value = t("Inbox")
selectedTag.value = null
fetchPayload = {
"order[sendDate]": "desc",
itemsPerPage: initialRowsPerPage,
page: 1,
"receivers.receiver": securityStore.user["@id"],
"receivers.receiverType": MESSAGE_TYPE_INBOX,
}
loadMessages()
}
function showInboxByTag(tag) {
showingInbox.value = true
title.value = tag.tag
selectedTag.value = tag
fetchPayload = {
"order[sendDate]": "desc",
itemsPerPage: initialRowsPerPage,
page: 1,
"receivers.receiverType": MESSAGE_TYPE_INBOX,
}
loadMessages()
}
function showUnread() {
showingInbox.value = true
title.value = t("Unread")
selectedTag.value = null
fetchPayload = {
"order[sendDate]": "desc",
"receivers.read": false,
itemsPerPage: initialRowsPerPage,
page: 1,
"receivers.receiverType": MESSAGE_TYPE_INBOX,
}
loadMessages()
}
function showSent() {
showingInbox.value = false
title.value = t("Sent")
selectedTag.value = null
fetchPayload = {
sender: securityStore.user["@id"],
"receivers.receiverType": MESSAGE_TYPE_SENDER,
"order[sendDate]": "desc",
itemsPerPage: initialRowsPerPage,
page: 1,
}
loadMessages()
}
function refreshMessages() {
fetchPayload.itemsPerPage = initialRowsPerPage
fetchPayload.page = 1
loadMessages()
}
function onPage(event) {
delete fetchPayload["order[title]"]
delete fetchPayload["order[sendDate]"]
fetchPayload.page = event.page + 1
fetchPayload.itemsPerPage = event.rows
fetchPayload[`order[${event.sortField}]`] = event.sortOrder === -1 ? "desc" : "asc"
loadMessages(false)
}
function sortingChanged(event) {
delete fetchPayload["order[title]"]
delete fetchPayload["order[sendDate]"]
fetchPayload[`order[${event.sortField}]`] = event.sortOrder === -1 ? "desc" : "asc"
loadMessages(true)
}
function findMyReceiver(message) {
const receivers = [...message.receiversTo, ...message.receiversCc, ...message.receiversSender]
return receivers.find(({ receiver }) => receiver["@id"] === securityStore.user["@id"])
}
async function deleteMessage(message) {
try {
const myReceiver = findMyReceiver(message)
if (myReceiver) {
await store.dispatch("messagereluser/del", myReceiver)
notification.showSuccessNotification(t("Message deleted"))
}
await messageRelUserStore.findUnreadCount()
loadMessages()
} catch (e) {
notification.showErrorNotification(t("Error deleting message"))
}
}
function showDlgConfirmDeleteSingle(dataOrItem) {
const item = dataOrItem.data || dataOrItem
confirm.require({
header: t("Confirmation"),
message: t("Are you sure you want to delete %s?", [item.title]),
accept: async () => {
await deleteMessage(item)
},
})
}
function showDlgConfirmDeleteMultiple() {
confirm.require({
header: t("Confirmation"),
message: t("Are you sure you want to delete the selected items?"),
accept: async () => {
for (const message of selectedItems.value) {
await deleteMessage(message)
}
selectedItems.value = []
loadMessages()
},
})
}
onMounted(() => {
showInbox()
})
function onSearch() {
fetchPayload = {
"order[sendDate]": "desc",
itemsPerPage: initialRowsPerPage,
page: 1,
}
loadMessages()
}
function onResetSearch() {
searchText.value = ""
fetchPayload = {
"order[sendDate]": "desc",
itemsPerPage: initialRowsPerPage,
page: 1,
}
loadMessages()
}
</script>