@bildvitta/quasar-ui-asteroid 3.14.0-beta.2 → 3.14.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/app-bar/QasAppBar.vue +6 -1
- package/src/components/app-bar/QasAppBar.yml +3 -0
- package/src/components/app-menu/QasAppMenu.vue +3 -2
- package/src/components/app-menu/QasAppMenu.yml +3 -0
- package/src/components/app-menu/composables/use-app-user.js +3 -0
- package/src/components/app-user/QasAppUser.vue +68 -13
- package/src/components/app-user/QasAppUser.yml +3 -0
- package/src/components/avatar/QasAvatar.vue +9 -3
- package/src/components/avatar/QasAvatar.yml +1 -1
- package/src/components/avatar/enums/AvatarColors.js +2 -1
- package/src/components/card/QasCard.vue +3 -3
- package/src/components/date/QasDate.vue +31 -36
- package/src/components/dialog/QasDialog.vue +29 -1
- package/src/components/drawer/QasDrawer.vue +117 -0
- package/src/components/drawer/QasDrawer.yml +57 -0
- package/src/components/infinite-scroll/QasInfiniteScroll.vue +6 -2
- package/src/components/infinite-scroll/QasInfiniteScroll.yml +22 -0
- package/src/components/layout/QasLayout.vue +83 -52
- package/src/components/layout/QasLayout.yml +5 -0
- package/src/components/layout/private/PvLayoutNotificationCard.vue +86 -0
- package/src/components/layout/private/PvLayoutNotificationsDrawer.vue +141 -0
- package/src/components/list-items/QasListItems.vue +33 -5
- package/src/components/list-items/QasListItems.yml +5 -0
- package/src/components/whatsapp-link/QasWhatsappLink.vue +2 -2
- package/src/components/whatsapp-link/QasWhatsappLink.yml +1 -1
- package/src/composables/index.js +3 -0
- package/src/composables/use-notifications.js +114 -0
- package/src/css/plugins/notify.scss +40 -2
- package/src/helpers/private/has-parent-by-class-name.js +15 -0
- package/src/vue-plugin.js +3 -0
|
@@ -62,7 +62,7 @@ const props = defineProps({
|
|
|
62
62
|
|
|
63
63
|
defineExpose({ refresh, remove })
|
|
64
64
|
|
|
65
|
-
const emit = defineEmits(['update:list'])
|
|
65
|
+
const emit = defineEmits(['update:list', 'fetch-success', 'fetch-error'])
|
|
66
66
|
|
|
67
67
|
const axios = inject('axios')
|
|
68
68
|
|
|
@@ -135,10 +135,14 @@ async function fetchList () {
|
|
|
135
135
|
* após buscar uma vez e retornar uma lista vazia.
|
|
136
136
|
*/
|
|
137
137
|
hasMadeFirstFetch.value = true
|
|
138
|
-
|
|
138
|
+
|
|
139
|
+
emit('fetch-success', { list: newList, offset: offset.value, count: count.value })
|
|
140
|
+
} catch (error) {
|
|
139
141
|
NotifyError('Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.')
|
|
140
142
|
|
|
141
143
|
hasFetchingError.value = true
|
|
144
|
+
|
|
145
|
+
emit('fetch-error', error)
|
|
142
146
|
} finally {
|
|
143
147
|
isFetching.value = false
|
|
144
148
|
}
|
|
@@ -44,6 +44,28 @@ events:
|
|
|
44
44
|
desc: Novo valor do list
|
|
45
45
|
type: Array
|
|
46
46
|
|
|
47
|
+
'@fetch-error -> function (error)':
|
|
48
|
+
desc: Dispara toda vez que ocorre algum erro ao fazer nova busca na API.
|
|
49
|
+
params:
|
|
50
|
+
error:
|
|
51
|
+
count: Erro enviado da API.
|
|
52
|
+
type: Object
|
|
53
|
+
|
|
54
|
+
'@fetch-success -> function ({ count, list, offset })':
|
|
55
|
+
desc: Dispara toda vez que é feito uma nova busca com sucesso na API.
|
|
56
|
+
params:
|
|
57
|
+
count:
|
|
58
|
+
count: Tamanho máximo de itens.
|
|
59
|
+
type: Number
|
|
60
|
+
|
|
61
|
+
list:
|
|
62
|
+
desc: Lista do endpoint.
|
|
63
|
+
type: Array
|
|
64
|
+
|
|
65
|
+
offset:
|
|
66
|
+
desc: valor atual que indica a posição inicial ao recuperar resultados da lista.
|
|
67
|
+
type: Number
|
|
68
|
+
|
|
47
69
|
slots:
|
|
48
70
|
default:
|
|
49
71
|
desc: slot para exibir a lista na qual o componente fez a busca.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<q-layout view="hHh Lpr lff">
|
|
3
3
|
<slot v-if="$qas.screen.untilLarge" name="app-bar">
|
|
4
|
-
<qas-app-bar v-bind="appBarProps" @sign-out="signOut" @toggle-menu="toggleMenuDrawer" />
|
|
4
|
+
<qas-app-bar v-bind="appBarProps" @sign-out="signOut" @toggle-menu="toggleMenuDrawer" @toggle-notifications="toggleNotificationsDrawer" />
|
|
5
5
|
</slot>
|
|
6
6
|
|
|
7
7
|
<slot name="app-menu">
|
|
8
|
-
<qas-app-menu :model-value="showMenuDrawer" v-bind="defaultAppMenuProps" @sign-out="signOut" @update:model-value="updateMenuDrawer" />
|
|
8
|
+
<qas-app-menu :model-value="showMenuDrawer" v-bind="defaultAppMenuProps" @sign-out="signOut" @toggle-notifications="toggleNotificationsDrawer" @update:model-value="updateMenuDrawer" />
|
|
9
9
|
</slot>
|
|
10
10
|
|
|
11
11
|
<slot>
|
|
@@ -17,72 +17,103 @@
|
|
|
17
17
|
</slot>
|
|
18
18
|
|
|
19
19
|
<q-ajax-bar color="primary" position="bottom" size="2px" />
|
|
20
|
+
|
|
21
|
+
<pv-layout-notifications-drawer v-if="isNotificationsEnabled" v-model="notificationsDrawer" />
|
|
20
22
|
</q-layout>
|
|
21
23
|
</template>
|
|
22
24
|
|
|
23
|
-
<script>
|
|
25
|
+
<script setup>
|
|
26
|
+
import PvLayoutNotificationsDrawer from './private/PvLayoutNotificationsDrawer.vue'
|
|
24
27
|
import QasAppBar from '../app-bar/QasAppBar.vue'
|
|
25
28
|
import QasAppMenu from '../app-menu/QasAppMenu.vue'
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
import useScreen from '../../composables/use-screen'
|
|
31
|
+
import useNotifications from '../../composables/use-notifications'
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
QasAppBar,
|
|
32
|
-
QasAppMenu
|
|
33
|
-
},
|
|
33
|
+
import { computed, ref, watch } from 'vue'
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
appBarProps: {
|
|
37
|
-
default: () => ({}),
|
|
38
|
-
type: Object
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
appMenuProps: {
|
|
42
|
-
default: () => ({}),
|
|
43
|
-
type: Object
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
modelValue: {
|
|
47
|
-
default: true,
|
|
48
|
-
type: Boolean
|
|
49
|
-
}
|
|
50
|
-
},
|
|
35
|
+
defineOptions({ name: 'QasLayout' })
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
appBarProps: {
|
|
39
|
+
default: () => ({}),
|
|
40
|
+
type: Object
|
|
41
|
+
},
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
43
|
+
appMenuProps: {
|
|
44
|
+
default: () => ({}),
|
|
45
|
+
type: Object
|
|
58
46
|
},
|
|
59
47
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
...this.appBarProps,
|
|
64
|
-
...this.appMenuProps
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
showMenuDrawer () {
|
|
69
|
-
return !this.$qas.screen.untilLarge || this.menuDrawer
|
|
70
|
-
}
|
|
48
|
+
initialUnreadNotificationsCount: {
|
|
49
|
+
type: Number,
|
|
50
|
+
default: 0
|
|
71
51
|
},
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
53
|
+
modelValue: {
|
|
54
|
+
default: true,
|
|
55
|
+
type: Boolean
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const emit = defineEmits(['sign-out', 'update:modelValue'])
|
|
60
|
+
|
|
61
|
+
// expondo método "toggleNotificationsDrawer" para fora do componente.
|
|
62
|
+
defineExpose({ toggleNotificationsDrawer })
|
|
63
|
+
|
|
64
|
+
const screen = useScreen()
|
|
65
|
+
|
|
66
|
+
const { isNotificationsEnabled, setUnreadNotificationsCount } = useNotifications()
|
|
67
|
+
|
|
68
|
+
const menuDrawer = ref(false)
|
|
69
|
+
const notificationsDrawer = ref(false)
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Como está sendo utilizado em um watcher com a propriedade 'immediate: true',
|
|
73
|
+
* é necessário criar a variável antes de atribuí-la ao watcher, para assim conseguir pará-lo.
|
|
74
|
+
*/
|
|
75
|
+
let unreadNotificationsCountWatcher = () => {}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// computed
|
|
78
|
+
const defaultAppMenuProps = computed(() => {
|
|
79
|
+
return {
|
|
80
|
+
...props.appBarProps,
|
|
81
|
+
...props.appMenuProps
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const showMenuDrawer = computed(() => !screen.untilLarge || menuDrawer.value)
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A propriedade "initialUnreadNotificationsCount" é escutada apenas uma vez,
|
|
89
|
+
* quando ela é iniciada, seta o "unreadNotificationsCount" do composable,
|
|
90
|
+
* após isto quem controla é o QasLayout.
|
|
91
|
+
*/
|
|
92
|
+
unreadNotificationsCountWatcher = watch(() => props.initialUnreadNotificationsCount, value => {
|
|
93
|
+
if (value) {
|
|
94
|
+
setUnreadNotificationsCount(value)
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.$emit('update:modelValue', value)
|
|
85
|
-
}
|
|
96
|
+
// finaliza o watcher
|
|
97
|
+
unreadNotificationsCountWatcher()
|
|
86
98
|
}
|
|
99
|
+
}, { immediate: true })
|
|
100
|
+
|
|
101
|
+
// functions
|
|
102
|
+
function signOut () {
|
|
103
|
+
emit('sign-out')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toggleMenuDrawer () {
|
|
107
|
+
updateMenuDrawer(!menuDrawer.value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function updateMenuDrawer (value) {
|
|
111
|
+
menuDrawer.value = value
|
|
112
|
+
|
|
113
|
+
emit('update:modelValue', value)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toggleNotificationsDrawer () {
|
|
117
|
+
notificationsDrawer.value = !notificationsDrawer.value
|
|
87
118
|
}
|
|
88
119
|
</script>
|
|
@@ -14,6 +14,11 @@ props:
|
|
|
14
14
|
default: { animation: 500 }
|
|
15
15
|
type: Object
|
|
16
16
|
|
|
17
|
+
initial-unread-notifications-count:
|
|
18
|
+
desc: Propriedade para indicar quantas notificações não lidas existem.
|
|
19
|
+
default: 0
|
|
20
|
+
type: Number
|
|
21
|
+
|
|
17
22
|
model-value:
|
|
18
23
|
desc: Model do componente, responsável por abrir/fechar menu lateral.
|
|
19
24
|
default: true
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="items-center justify-between no-wrap row">
|
|
3
|
+
<div class="items-center row">
|
|
4
|
+
<div class="q-mr-sm">
|
|
5
|
+
<q-icon :color="iconColor" name="sym_r_info" size="md" />
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div>
|
|
9
|
+
<span class="text-caption text-grey-6">
|
|
10
|
+
{{ dateLabel }}
|
|
11
|
+
</span>
|
|
12
|
+
|
|
13
|
+
<div class="items-center q-mt-xs row">
|
|
14
|
+
<h6 class="text-subtitle1" :class="titleClass">
|
|
15
|
+
{{ props.notification.title }}
|
|
16
|
+
</h6>
|
|
17
|
+
|
|
18
|
+
<div v-if="hasBadge" class="q-ml-sm">
|
|
19
|
+
<qas-badge color="indigo-1" label="Nova" text-color="grey-10" />
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="q-mt-xs text-body1 text-grey-8">
|
|
24
|
+
{{ props.notification.message }}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<qas-btn v-if="props.notification.link" v-bind="buttonProps" />
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup>
|
|
34
|
+
import { dateTime } from '../../../helpers/filters'
|
|
35
|
+
|
|
36
|
+
import { computed } from 'vue'
|
|
37
|
+
import { date } from 'quasar'
|
|
38
|
+
|
|
39
|
+
defineOptions({ name: 'PvLayoutNotificationCard' })
|
|
40
|
+
|
|
41
|
+
const props = defineProps({
|
|
42
|
+
notification: {
|
|
43
|
+
type: Object,
|
|
44
|
+
default: () => ({})
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const markedAsRead = computed(() => props.notification.isRead)
|
|
49
|
+
|
|
50
|
+
const iconColor = computed(() => markedAsRead.value ? 'grey-8' : 'primary')
|
|
51
|
+
const titleClass = computed(() => markedAsRead.value ? 'text-grey-8' : 'text-grey-10')
|
|
52
|
+
|
|
53
|
+
const isRecentNotification = computed(() => {
|
|
54
|
+
const currentDate = new Date().toISOString()
|
|
55
|
+
|
|
56
|
+
return date.getDateDiff(currentDate, props.notification.createdAt, 'minutes') < 10
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const hasBadge = computed(() => isRecentNotification.value && !markedAsRead.value)
|
|
60
|
+
|
|
61
|
+
const dateLabel = computed(() => {
|
|
62
|
+
return isRecentNotification.value
|
|
63
|
+
? 'Agora mesmo'
|
|
64
|
+
: dateTime(props.notification.createdAt, 'dd/MM/yyyy HH:mm')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const buttonProps = computed(() => {
|
|
68
|
+
const { link } = props.notification
|
|
69
|
+
|
|
70
|
+
const urlFromLink = new URL(link)
|
|
71
|
+
|
|
72
|
+
const isExternalURL = urlFromLink.host !== location.host
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
icon: 'sym_r_chevron_right',
|
|
76
|
+
color: markedAsRead.value ? 'grey-10' : 'primary',
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Se for uma url externa, diferente da URL que ele esta acessando no momento,
|
|
80
|
+
* é adicionado um "href" porque é um link para outro modulo, caso seja para o mesmo modulo,
|
|
81
|
+
* é adicionado um "to" para não recarregar a pagina.
|
|
82
|
+
*/
|
|
83
|
+
...(isExternalURL ? { href: link } : { to: urlFromLink.pathname })
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
</script>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<qas-drawer v-bind="drawerProps" v-model="model">
|
|
3
|
+
<div class="fixed-position">
|
|
4
|
+
<div class="justify-end row">
|
|
5
|
+
<qas-btn class="q-mb-xl" :disable="isAllNotificationsRead" icon="sym_r_check_circle" label="Marcar todas como lida" @click="markAsRead" />
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<qas-infinite-scroll v-model:list="notifications" v-bind="infiniteScrollProps">
|
|
9
|
+
<qas-list-items :list="notifications" :use-box="false" :use-clickable-item="false" :use-section-actions="false">
|
|
10
|
+
<template #item-section="{ item }">
|
|
11
|
+
<pv-layout-notification-card :notification="item" />
|
|
12
|
+
</template>
|
|
13
|
+
</qas-list-items>
|
|
14
|
+
</qas-infinite-scroll>
|
|
15
|
+
</div>
|
|
16
|
+
</qas-drawer>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup>
|
|
20
|
+
import PvLayoutNotificationCard from './PvLayoutNotificationCard.vue'
|
|
21
|
+
import QasDrawer from '../../drawer/QasDrawer.vue'
|
|
22
|
+
import QasInfiniteScroll from '../../infinite-scroll/QasInfiniteScroll.vue'
|
|
23
|
+
|
|
24
|
+
import useNotifications, { onNotificationReceived } from '../../../composables/use-notifications'
|
|
25
|
+
|
|
26
|
+
import { promiseHandler } from '../../../helpers'
|
|
27
|
+
|
|
28
|
+
import { computed, ref, inject, onMounted } from 'vue'
|
|
29
|
+
|
|
30
|
+
defineOptions({ name: 'PvLayoutNotificationsDrawer' })
|
|
31
|
+
|
|
32
|
+
const props = defineProps({
|
|
33
|
+
model: {
|
|
34
|
+
type: Boolean
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits(['update:modelValue'])
|
|
39
|
+
|
|
40
|
+
const axios = inject('axios')
|
|
41
|
+
|
|
42
|
+
const { setUnreadNotificationsCount } = useNotifications()
|
|
43
|
+
|
|
44
|
+
const hasMadeFirstFetch = ref(false)
|
|
45
|
+
const isMarkingNotificationsAsRead = ref(false)
|
|
46
|
+
|
|
47
|
+
const notifications = ref([])
|
|
48
|
+
|
|
49
|
+
onMounted(() => {
|
|
50
|
+
const notificationsUtilsChannel = new BroadcastChannel('notifications--utils')
|
|
51
|
+
|
|
52
|
+
notificationsUtilsChannel.onmessage = ({ data }) => {
|
|
53
|
+
if (data.type === 'markAllAsRead') onMarkAsReadSuccess()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Quando o usuário ainda não abriu a central de notificações, a primeira vez que
|
|
59
|
+
* ele abrir vai obter esses dados via API, mesmo que ele tenha recebido a notificação
|
|
60
|
+
* em real time, após ele ter aberto as notificações pelo menos uma vez, todas
|
|
61
|
+
* notificações recebidas em real time serão incrementadas na central de
|
|
62
|
+
* notificação sem a necessidade de chamar a API e resetar a paginação (feita por scroll).
|
|
63
|
+
*/
|
|
64
|
+
onNotificationReceived(notification => {
|
|
65
|
+
if (!hasMadeFirstFetch.value) return
|
|
66
|
+
|
|
67
|
+
notifications.value.unshift(notification)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const infiniteScrollProps = {
|
|
71
|
+
limitPerPage: 30,
|
|
72
|
+
// os "165px" são referentes ao cabeçalho.
|
|
73
|
+
maxHeight: 'calc(100vh - 165px)',
|
|
74
|
+
url: 'users/me/notifications',
|
|
75
|
+
onFetchSuccess
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// computed
|
|
79
|
+
const model = computed({
|
|
80
|
+
get () {
|
|
81
|
+
return props.model
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
set (value) {
|
|
85
|
+
emit('update:modelValue', value)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const drawerProps = computed(() => {
|
|
90
|
+
return {
|
|
91
|
+
loading: isMarkingNotificationsAsRead.value,
|
|
92
|
+
maxWidth: '60%',
|
|
93
|
+
title: 'Notificações'
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Se todas notificações estiverem lidas, então desabilitar o botão de "Marcar todas como lida"
|
|
99
|
+
*/
|
|
100
|
+
const isAllNotificationsRead = computed(() => !notifications.value.some(notification => !notification?.isRead))
|
|
101
|
+
|
|
102
|
+
// functions
|
|
103
|
+
async function markAsRead () {
|
|
104
|
+
const { data } = await promiseHandler(
|
|
105
|
+
axios.patch('/users/me/notifications', { markAllAsRead: true }),
|
|
106
|
+
{
|
|
107
|
+
useLoading: false,
|
|
108
|
+
errorMessage: 'Falha ao marcar todas notificações como lida. Por favor, tente novamente em alguns minutos.',
|
|
109
|
+
onLoading: isLoading => {
|
|
110
|
+
isMarkingNotificationsAsRead.value = isLoading
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if (data) {
|
|
116
|
+
const notificationsUtilsChannel = new BroadcastChannel('notifications--utils')
|
|
117
|
+
|
|
118
|
+
notificationsUtilsChannel.postMessage({ type: 'markAllAsRead' })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Ao marcar todas notificações como lida, é necessário percorrer todo o array
|
|
124
|
+
* de "notifications" e setar a prop "isRead" como "true", para não precisar chamar
|
|
125
|
+
* novamente a API para atualizar estes dados.
|
|
126
|
+
*/
|
|
127
|
+
function onMarkAsReadSuccess () {
|
|
128
|
+
notifications.value.forEach(notification => {
|
|
129
|
+
notification.isRead = true
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Zera o contador de notificações, responsável pelo ícone no QasAppUser.
|
|
134
|
+
*/
|
|
135
|
+
setUnreadNotificationsCount(0)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onFetchSuccess () {
|
|
139
|
+
hasMadeFirstFetch.value = true
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component :is="component" class="qas-list-items" :class="classes">
|
|
3
3
|
<q-list separator>
|
|
4
4
|
<q-item v-for="(item, index) in props.list" :key="index" v-ripple :clickable="props.useClickableItem" @click="onClick({ item, index }, true)">
|
|
5
5
|
<slot :index="index" :item="item" name="item">
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
</slot>
|
|
16
16
|
</q-item>
|
|
17
17
|
</q-list>
|
|
18
|
-
</
|
|
18
|
+
</component>
|
|
19
19
|
</template>
|
|
20
20
|
|
|
21
21
|
<script setup>
|
|
22
|
-
import QasBox from '../box/QasBox.vue'
|
|
23
22
|
import QasBtn from '../btn/QasBtn.vue'
|
|
24
23
|
|
|
24
|
+
import { computed } from 'vue'
|
|
25
|
+
|
|
25
26
|
defineOptions({ name: 'QasListItems' })
|
|
26
27
|
|
|
27
28
|
const props = defineProps({
|
|
@@ -35,6 +36,11 @@ const props = defineProps({
|
|
|
35
36
|
type: Array
|
|
36
37
|
},
|
|
37
38
|
|
|
39
|
+
useBox: {
|
|
40
|
+
type: Boolean,
|
|
41
|
+
default: true
|
|
42
|
+
},
|
|
43
|
+
|
|
38
44
|
useClickableItem: {
|
|
39
45
|
type: Boolean
|
|
40
46
|
},
|
|
@@ -47,6 +53,10 @@ const props = defineProps({
|
|
|
47
53
|
|
|
48
54
|
const emit = defineEmits(['click-item'])
|
|
49
55
|
|
|
56
|
+
const classes = computed(() => ({ 'qas-list-items--no-click': !props.useClickableItem }))
|
|
57
|
+
|
|
58
|
+
const component = computed(() => props.useBox ? 'qas-box' : 'div')
|
|
59
|
+
|
|
50
60
|
function onClick ({ item, index }, fromItem) {
|
|
51
61
|
/**
|
|
52
62
|
* se o click veio do q-item e "useClickableItem" for "false", ou
|
|
@@ -62,8 +72,26 @@ function onClick ({ item, index }, fromItem) {
|
|
|
62
72
|
|
|
63
73
|
<style lang="scss">
|
|
64
74
|
.qas-list-items {
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
&--no-click {
|
|
76
|
+
.q-item {
|
|
77
|
+
.q-ripple {
|
|
78
|
+
display: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.q-list {
|
|
84
|
+
& > .q-item {
|
|
85
|
+
padding: var(--qas-spacing-lg) 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
& > .q-item:last-child {
|
|
89
|
+
padding-bottom: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
& > .q-item:first-child {
|
|
93
|
+
padding-top: 0;
|
|
94
|
+
}
|
|
67
95
|
}
|
|
68
96
|
}
|
|
69
97
|
</style>
|
|
@@ -14,6 +14,11 @@ props:
|
|
|
14
14
|
default: []
|
|
15
15
|
type: Array
|
|
16
16
|
|
|
17
|
+
use-box:
|
|
18
|
+
desc: Controla se o vai ser um QasBox ou div.
|
|
19
|
+
type: Boolean
|
|
20
|
+
default: true
|
|
21
|
+
|
|
17
22
|
use-clickable-item:
|
|
18
23
|
desc: Controla se o item inteiro é clicável ou somente o button dentro do item.
|
|
19
24
|
type: Boolean
|
package/src/composables/index.js
CHANGED
|
@@ -3,3 +3,6 @@ export { default as useForm } from './use-form.js'
|
|
|
3
3
|
export { default as useHistory } from './use-history.js'
|
|
4
4
|
export { default as useQueryCache } from './use-query-cache.js'
|
|
5
5
|
export { default as useScreen } from './use-screen.js'
|
|
6
|
+
export { default as useNotifications } from './use-notifications.js'
|
|
7
|
+
|
|
8
|
+
export * from './use-notifications.js'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import asteroidConfig from 'asteroid-config'
|
|
2
|
+
import hasParentByClassName from '../helpers/private/has-parent-by-class-name'
|
|
3
|
+
|
|
4
|
+
import { Notify } from 'quasar'
|
|
5
|
+
import { ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
const callbackFunctions = {
|
|
8
|
+
onNotificationReceived: []
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function onNotificationReceived (callbackFn) {
|
|
12
|
+
callbackFunctions.onNotificationReceived.push(callbackFn)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const unreadNotificationsCount = ref(0)
|
|
16
|
+
|
|
17
|
+
export default function () {
|
|
18
|
+
const isNotificationsEnabled = asteroidConfig.framework.featureToggle?.useNotifications
|
|
19
|
+
|
|
20
|
+
function triggerNotification (notification) {
|
|
21
|
+
callbackFunctions.onNotificationReceived.forEach(fn => fn((notification)))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setUnreadNotificationsCount (value) {
|
|
25
|
+
unreadNotificationsCount.value = value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function incrementUnreadNotificationsCount () {
|
|
29
|
+
unreadNotificationsCount.value++
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {{
|
|
34
|
+
* message: string
|
|
35
|
+
* title: string
|
|
36
|
+
* link?: string
|
|
37
|
+
* }} config
|
|
38
|
+
*/
|
|
39
|
+
function sendNotify (config = {}) {
|
|
40
|
+
const { link } = config
|
|
41
|
+
|
|
42
|
+
const classes = ['bg-white', 'boot-notification', 'q-py-sm', 'text-grey-8']
|
|
43
|
+
|
|
44
|
+
if (link) classes.push(...['boot-notification--has-link', 'cursor-pointer'])
|
|
45
|
+
|
|
46
|
+
const closeNotify = Notify.create({
|
|
47
|
+
actions: [{
|
|
48
|
+
icon: 'sym_r_close',
|
|
49
|
+
class: 'boot-notification__close-button qas-btn qas-btn--tertiary qas-btn--tertiary-grey-10'
|
|
50
|
+
}],
|
|
51
|
+
|
|
52
|
+
attrs: {
|
|
53
|
+
onClick: event => onNotifyClick({ event, link, closeNotify })
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
classes: classes.join(' '),
|
|
57
|
+
html: true,
|
|
58
|
+
icon: 'sym_r_info',
|
|
59
|
+
iconColor: 'primary',
|
|
60
|
+
message: getHTMLMessage(config),
|
|
61
|
+
multiLine: true,
|
|
62
|
+
position: 'bottom-right',
|
|
63
|
+
timeout: 30000
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Função que é chamada quando o usuário clica na notificação, se a notificação
|
|
68
|
+
* tem link, então ele vai ser redirecionado para o link em uma nova aba, caso
|
|
69
|
+
* não tenha link, nada acontece.
|
|
70
|
+
*/
|
|
71
|
+
function onNotifyClick ({ event, link, closeNotify }) {
|
|
72
|
+
if (!link) return
|
|
73
|
+
|
|
74
|
+
if (hasParentByClassName('boot-notification__close-button', event.srcElement)) return
|
|
75
|
+
|
|
76
|
+
window.open(link)
|
|
77
|
+
|
|
78
|
+
closeNotify()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Função que retorna um HTML para seguir estilos propostos pelo design.
|
|
83
|
+
*/
|
|
84
|
+
function getHTMLMessage ({ message, title, icon = 'info' } = {}) {
|
|
85
|
+
return (`
|
|
86
|
+
<div>
|
|
87
|
+
<header class="row items-center">
|
|
88
|
+
<h5 class="text-grey-10 text-h5 boot-notification__title">
|
|
89
|
+
${title}
|
|
90
|
+
</h5>
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
<div class="q-mt-sm text-grey-8 text-body1">
|
|
94
|
+
${message}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
// const
|
|
103
|
+
isNotificationsEnabled,
|
|
104
|
+
|
|
105
|
+
// computed
|
|
106
|
+
unreadNotificationsCount,
|
|
107
|
+
|
|
108
|
+
// functions
|
|
109
|
+
incrementUnreadNotificationsCount,
|
|
110
|
+
sendNotify,
|
|
111
|
+
setUnreadNotificationsCount,
|
|
112
|
+
triggerNotification
|
|
113
|
+
}
|
|
114
|
+
}
|