255 lines
6.7 KiB
Vue
255 lines
6.7 KiB
Vue
<script lang="ts" setup>
|
|
// NOTE: Library
|
|
import { onMounted, reactive, ref } from 'vue';
|
|
|
|
// NOTE: Components
|
|
import NoData from 'src/components/NoData.vue';
|
|
import NotiDialog from './NotiDialog.vue';
|
|
|
|
// NOTE: Stores & Type
|
|
import { useNavigator } from 'src/stores/navigator';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useNotification } from 'src/stores/notification';
|
|
import { dateFormatJS } from 'src/utils/datetime';
|
|
|
|
// NOTE: Variable
|
|
const navigatorStore = useNavigator();
|
|
const notificationStore = useNotification();
|
|
const { data: noti } = storeToRefs(notificationStore);
|
|
|
|
const state = reactive({
|
|
currentTab: 'all',
|
|
inputSearch: '',
|
|
notiDialog: false,
|
|
notiId: '',
|
|
});
|
|
|
|
const pageTabs = [
|
|
{ label: 'all', value: 'all' },
|
|
{ label: 'unread', value: 'unread' },
|
|
];
|
|
|
|
const selectedNoti = ref<string[]>([]);
|
|
|
|
function toggleSelection(id: string, checkAll: boolean = false) {
|
|
const index = selectedNoti.value.indexOf(id);
|
|
if (checkAll) {
|
|
if (selectedNoti.value.length === noti.value.length) {
|
|
selectedNoti.value = [];
|
|
} else {
|
|
selectedNoti.value = [...noti.value.map((v) => v.id)];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (index > -1) {
|
|
selectedNoti.value.splice(index, 1);
|
|
} else {
|
|
selectedNoti.value.push(id);
|
|
}
|
|
}
|
|
|
|
async function fetchNoti() {
|
|
const res = await notificationStore.getNotificationList();
|
|
if (res) {
|
|
noti.value = res.result;
|
|
}
|
|
}
|
|
|
|
async function markAsRead() {
|
|
const res = await notificationStore.markReadNotification(selectedNoti.value);
|
|
if (res) {
|
|
await fetchNoti();
|
|
selectedNoti.value = [];
|
|
}
|
|
}
|
|
|
|
async function deleteNoti() {
|
|
const res = await notificationStore.deleteMultiNotification(
|
|
selectedNoti.value,
|
|
);
|
|
if (res) {
|
|
await fetchNoti();
|
|
selectedNoti.value = [];
|
|
}
|
|
}
|
|
|
|
function readNoti(id: string) {
|
|
const notification = noti.value.find((n) => n.id === id);
|
|
|
|
state.notiId = id;
|
|
state.notiDialog = true;
|
|
if (notification) {
|
|
notification.read = true;
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
navigatorStore.current.title = 'noti.title';
|
|
navigatorStore.current.path = [{ text: 'noti.caption', i18n: true }];
|
|
|
|
await fetchNoti();
|
|
});
|
|
</script>
|
|
<template>
|
|
<main class="column full-height no-wrap">
|
|
<div class="surface-1 col bordered rounded column">
|
|
<div class="q-py-xs row items-center" style="padding-inline: 38px">
|
|
<q-checkbox
|
|
size="xs"
|
|
:model-value="noti.length > 0 && selectedNoti.length === noti.length"
|
|
:disable="noti.length === 0"
|
|
@click="toggleSelection('', true)"
|
|
/>
|
|
<q-separator vertical inset spaced="md" />
|
|
<q-btn
|
|
v-if="selectedNoti.length === 0"
|
|
icon="mdi-refresh"
|
|
rounded
|
|
flat
|
|
dense
|
|
size="sm"
|
|
class="app-text-muted-2 q-mt-xs"
|
|
@click="async () => await fetchNoti()"
|
|
>
|
|
<q-tooltip>Refresh</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="selectedNoti.length > 0"
|
|
icon="mdi-trash-can-outline"
|
|
rounded
|
|
flat
|
|
dense
|
|
size="sm"
|
|
class="app-text-muted-2"
|
|
@click="async () => await deleteNoti()"
|
|
>
|
|
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="selectedNoti.length > 0"
|
|
icon="mdi-email-open-outline"
|
|
rounded
|
|
flat
|
|
dense
|
|
size="sm"
|
|
class="app-text-muted-2 q-mx-sm"
|
|
@click="async () => await markAsRead()"
|
|
>
|
|
<q-tooltip>{{ $t('noti.markAsRead') }}</q-tooltip>
|
|
</q-btn>
|
|
<span v-if="selectedNoti.length" class="text-caption app-text-muted-2">
|
|
{{
|
|
$t('general.selected', {
|
|
number: selectedNoti.length,
|
|
msg: $t('noti.title'),
|
|
})
|
|
}}
|
|
</span>
|
|
</div>
|
|
|
|
<q-tabs
|
|
inline-label
|
|
mobile-arrows
|
|
dense
|
|
v-model="state.currentTab"
|
|
align="left"
|
|
class="full-width bordered-b"
|
|
active-color="info"
|
|
>
|
|
<q-tab
|
|
v-for="tab in pageTabs"
|
|
:name="tab.value"
|
|
:key="tab.value"
|
|
@click="
|
|
() => {
|
|
state.currentTab = tab.value;
|
|
state.inputSearch = '';
|
|
}
|
|
"
|
|
>
|
|
<div
|
|
class="row text-capitalize"
|
|
:class="
|
|
state.currentTab === tab.value ? 'text-bold' : 'app-text-muted'
|
|
"
|
|
>
|
|
{{ $t(`noti.${tab.label}`) }}
|
|
<q-badge
|
|
rounded
|
|
class="q-ml-md"
|
|
:color="
|
|
(tab.value === 'all'
|
|
? noti.length
|
|
: noti.filter((v) => !v.read).length) > 0
|
|
? 'info'
|
|
: 'grey'
|
|
"
|
|
>
|
|
{{
|
|
tab.value === 'all'
|
|
? noti.length
|
|
: noti.filter((v) => !v.read).length
|
|
}}
|
|
</q-badge>
|
|
</div>
|
|
</q-tab>
|
|
</q-tabs>
|
|
|
|
<section class="scroll full-height col">
|
|
<article
|
|
v-if="
|
|
state.currentTab === 'unread'
|
|
? noti.filter((v) => !v.read).length > 0
|
|
: noti.length > 0
|
|
"
|
|
class="q-pa-md q-gutter-sm"
|
|
>
|
|
<q-item
|
|
v-for="(n, i) in state.currentTab === 'unread'
|
|
? noti.filter((v) => !v.read)
|
|
: noti"
|
|
:key="i"
|
|
clickable
|
|
:class="{ 'surface-3': !n.read }"
|
|
class="q-py-sm q-px-md rounded row no-wrap items-center"
|
|
@click.stop="readNoti(n.id)"
|
|
>
|
|
<div
|
|
class="rounded"
|
|
:style="`background: hsl(var(--info-bg)/${n.read ? 0 : 1}); width: 6px; height: 6px`"
|
|
/>
|
|
<q-checkbox
|
|
:model-value="selectedNoti.includes(n.id)"
|
|
size="xs"
|
|
class="q-pr-lg"
|
|
@click.stop="toggleSelection(n.id)"
|
|
/>
|
|
<div class="column">
|
|
{{ n.title }}
|
|
<span class="text-caption app-text-muted">
|
|
{{ n.detail }}
|
|
</span>
|
|
</div>
|
|
<div class="q-ml-auto">
|
|
{{
|
|
dateFormatJS({
|
|
date: n.createdAt,
|
|
dayStyle: 'numeric',
|
|
monthStyle: '2-digit',
|
|
withTime: true,
|
|
})
|
|
}}
|
|
</div>
|
|
</q-item>
|
|
</article>
|
|
<div v-else class="full-height flex items-center justify-center">
|
|
<NoData />
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
|
|
<NotiDialog v-model="state.notiDialog" v-model:id="state.notiId" />
|
|
</template>
|
|
<style scoped></style>
|