Toast system_alert ganha botão de ação com deeplink
Novo <Toast group="system-alerts"> no AppLayout com template custom (vive no bloco global — persiste em qualquer layout/rota). Renderiza: - Ícone de alerta + título em bold - Detail em texto menor com opacity - Botão com deeplink quando payload.deeplink existe, severity danger Label do botão inferido do deeplink: - /configuracoes/creditos-whatsapp → "Ir pra loja" - /configuracoes/whatsapp-pessoal → "Ver conexão" - /configuracoes/whatsapp-oficial → "Ver canal oficial" - outros → "Abrir" (ou payload.actionLabel se vier explícito) Clique navega via router.push se é path interno, senão window.location.href. Toast continua sticky (24h) + closable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,15 @@ export function useNotifications() {
|
|||||||
let ownerId = null;
|
let ownerId = null;
|
||||||
let pollTimer = null;
|
let pollTimer = null;
|
||||||
|
|
||||||
|
// Label curto do botão do toast baseado no deeplink
|
||||||
|
function defaultActionLabel(deeplink) {
|
||||||
|
if (!deeplink) return null;
|
||||||
|
if (deeplink.includes('creditos-whatsapp')) return 'Ir pra loja';
|
||||||
|
if (deeplink.includes('whatsapp-pessoal')) return 'Ver conexão';
|
||||||
|
if (deeplink.includes('whatsapp-oficial')) return 'Ver canal oficial';
|
||||||
|
return 'Abrir';
|
||||||
|
}
|
||||||
|
|
||||||
function showToastFor(item) {
|
function showToastFor(item) {
|
||||||
if (item?.type !== 'system_alert') return;
|
if (item?.type !== 'system_alert') return;
|
||||||
if (alertedIds.has(item.id)) return;
|
if (alertedIds.has(item.id)) return;
|
||||||
@@ -43,7 +52,12 @@ export function useNotifications() {
|
|||||||
summary: payload.title || 'Alerta do sistema',
|
summary: payload.title || 'Alerta do sistema',
|
||||||
detail: payload.detail || '',
|
detail: payload.detail || '',
|
||||||
life: STICKY_TOAST_LIFE_MS,
|
life: STICKY_TOAST_LIFE_MS,
|
||||||
closable: true
|
closable: true,
|
||||||
|
group: 'system-alerts',
|
||||||
|
data: {
|
||||||
|
deeplink: payload.deeplink,
|
||||||
|
actionLabel: payload.actionLabel || defaultActionLabel(payload.deeplink)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,18 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useLayout } from '@/layout/composables/layout';
|
import { useLayout } from '@/layout/composables/layout';
|
||||||
import { computed, onMounted, onBeforeUnmount, provide, watch } from 'vue';
|
import { computed, onMounted, onBeforeUnmount, provide, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
function goToDeeplink(link) {
|
||||||
|
if (!link) return;
|
||||||
|
if (typeof link === 'string' && link.startsWith('/')) {
|
||||||
|
router.push(link);
|
||||||
|
} else {
|
||||||
|
window.location.href = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
import AppFooter from './AppFooter.vue';
|
import AppFooter from './AppFooter.vue';
|
||||||
import AppSidebar from './AppSidebar.vue';
|
import AppSidebar from './AppSidebar.vue';
|
||||||
@@ -175,6 +186,32 @@ onBeforeUnmount(() => {
|
|||||||
<GlobalNoticeBanner />
|
<GlobalNoticeBanner />
|
||||||
<ConversationDrawer />
|
<ConversationDrawer />
|
||||||
<GlobalInboundNotifier />
|
<GlobalInboundNotifier />
|
||||||
|
|
||||||
|
<!-- Toast especial pra alertas de sistema (heartbeat, saldo baixo, etc) —
|
||||||
|
vermelho sticky com botão de ação opcional via payload.deeplink -->
|
||||||
|
<Toast group="system-alerts" position="top-right" :pt="{ root: { style: 'width: 28rem; max-width: 92vw' } }">
|
||||||
|
<template #message="slotProps">
|
||||||
|
<div class="flex flex-col gap-2 w-full">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<i class="pi pi-exclamation-circle text-lg mt-0.5" style="color: var(--p-red-500)" />
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="font-bold text-sm">{{ slotProps.message.summary }}</div>
|
||||||
|
<div class="text-xs mt-0.5 leading-relaxed opacity-85">{{ slotProps.message.detail }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
v-if="slotProps.message.data?.deeplink"
|
||||||
|
:label="slotProps.message.data?.actionLabel || 'Abrir'"
|
||||||
|
icon="pi pi-arrow-right"
|
||||||
|
iconPos="right"
|
||||||
|
size="small"
|
||||||
|
severity="danger"
|
||||||
|
class="rounded-full self-end"
|
||||||
|
@click="goToDeeplink(slotProps.message.data.deeplink)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Toast>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user