|
|
|
@ -1,5 +1,5 @@ |
|
|
|
|
<template> |
|
|
|
|
<div class="message-list"> |
|
|
|
|
<div class="message-list flex flex-col"> |
|
|
|
|
<SectionHeader :title="title"> |
|
|
|
|
<BaseButton |
|
|
|
|
icon="email-plus" |
|
|
|
@ -46,6 +46,7 @@ |
|
|
|
|
icon="inbox" |
|
|
|
|
type="black" |
|
|
|
|
@click="showInbox" |
|
|
|
|
class="w-full md:w-auto" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<BaseButton |
|
|
|
@ -53,6 +54,7 @@ |
|
|
|
|
icon="email-unread" |
|
|
|
|
type="black" |
|
|
|
|
@click="showUnread" |
|
|
|
|
class="w-full md:w-auto" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<BaseButton |
|
|
|
@ -60,6 +62,7 @@ |
|
|
|
|
icon="sent" |
|
|
|
|
type="black" |
|
|
|
|
@click="showSent" |
|
|
|
|
class="w-full md:w-auto" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<BaseButton |
|
|
|
@ -69,113 +72,199 @@ |
|
|
|
|
icon="tag-outline" |
|
|
|
|
type="black" |
|
|
|
|
@click="showInboxByTag(tag)" |
|
|
|
|
class="w-full md:w-auto" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<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" |
|
|
|
|
> |
|
|
|
|
<template #header> |
|
|
|
|
<form |
|
|
|
|
class="message-list__searcher-container" |
|
|
|
|
@submit.prevent="onSearch" |
|
|
|
|
<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" |
|
|
|
|
> |
|
|
|
|
<InputGroup> |
|
|
|
|
<InputText |
|
|
|
|
v-model="searchText" |
|
|
|
|
:placeholder="t('Search')" |
|
|
|
|
type="text" |
|
|
|
|
<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="search" |
|
|
|
|
type="primary" |
|
|
|
|
is-submit |
|
|
|
|
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="close" |
|
|
|
|
type="primary" |
|
|
|
|
@click="onResetSearch" |
|
|
|
|
icon="delete" |
|
|
|
|
size="small" |
|
|
|
|
type="danger" |
|
|
|
|
@click="showDlgConfirmDeleteSingle(item)" |
|
|
|
|
/> |
|
|
|
|
</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" |
|
|
|
|
> |
|
|
|
|
<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> |
|
|
|
|
</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> |
|
|
|
|
|
|
|
|
@ -310,6 +399,42 @@ const rowClass = (data) => { |
|
|
|
|
|
|
|
|
|
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") |
|
|
|
@ -450,12 +575,14 @@ async function deleteMessage(message) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function showDlgConfirmDeleteSingle({ data }) { |
|
|
|
|
function showDlgConfirmDeleteSingle(dataOrItem) { |
|
|
|
|
const item = dataOrItem.data || dataOrItem |
|
|
|
|
|
|
|
|
|
confirm.require({ |
|
|
|
|
header: t("Confirmation"), |
|
|
|
|
message: t("Are you sure you want to delete %s?", [data.title]), |
|
|
|
|
message: t("Are you sure you want to delete %s?", [item.title]), |
|
|
|
|
accept: async () => { |
|
|
|
|
await deleteMessage(data) |
|
|
|
|
await deleteMessage(item) |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|