Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
@@ -16,295 +16,288 @@
|
||||
-->
|
||||
<!-- Banner global no topo — position fixed acima da topbar, empurra layout via CSS var -->
|
||||
<script setup>
|
||||
import { watch, onBeforeUnmount, ref, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useNoticeStore } from '@/stores/noticeStore'
|
||||
import { useTenantStore } from '@/stores/tenantStore'
|
||||
import { watch, onBeforeUnmount, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useNoticeStore } from '@/stores/noticeStore';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const noticeStore = useNoticeStore()
|
||||
const tenantStore = useTenantStore()
|
||||
const router = useRouter()
|
||||
const noticeStore = useNoticeStore();
|
||||
const tenantStore = useTenantStore();
|
||||
const router = useRouter();
|
||||
|
||||
const bannerEl = ref(null)
|
||||
const visible = ref(false)
|
||||
const bannerEl = ref(null);
|
||||
const visible = ref(false);
|
||||
|
||||
// ── CSS variable: empurra topbar + layout ─────────────────────
|
||||
|
||||
function setHeight (px) {
|
||||
document.documentElement.style.setProperty('--notice-banner-height', px + 'px')
|
||||
function setHeight(px) {
|
||||
document.documentElement.style.setProperty('--notice-banner-height', px + 'px');
|
||||
}
|
||||
|
||||
function measureAndSet () {
|
||||
nextTick(() => {
|
||||
const h = bannerEl.value?.offsetHeight || 0
|
||||
setHeight(h)
|
||||
})
|
||||
function measureAndSet() {
|
||||
nextTick(() => {
|
||||
const h = bannerEl.value?.offsetHeight || 0;
|
||||
setHeight(h);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Reactive: notice muda → mostra/esconde ───────────────────
|
||||
|
||||
watch(
|
||||
() => noticeStore.activeNotice,
|
||||
(notice) => {
|
||||
if (notice) {
|
||||
visible.value = true
|
||||
measureAndSet()
|
||||
noticeStore.onView(notice)
|
||||
} else {
|
||||
visible.value = false
|
||||
setHeight(0)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
() => noticeStore.activeNotice,
|
||||
(notice) => {
|
||||
if (notice) {
|
||||
visible.value = true;
|
||||
measureAndSet();
|
||||
noticeStore.onView(notice);
|
||||
} else {
|
||||
visible.value = false;
|
||||
setHeight(0);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// ── Ação CTA ─────────────────────────────────────────────────
|
||||
|
||||
async function handleCta (notice) {
|
||||
await noticeStore.onCtaClick(notice)
|
||||
async function handleCta(notice) {
|
||||
await noticeStore.onCtaClick(notice);
|
||||
|
||||
if (notice.action_type === 'internal' && notice.action_route) {
|
||||
router.push(notice.action_route)
|
||||
} else if (notice.action_type === 'external' && notice.action_url) {
|
||||
const target = notice.link_target || '_blank'
|
||||
window.open(notice.action_url, target, 'noopener,noreferrer')
|
||||
}
|
||||
if (notice.action_type === 'internal' && notice.action_route) {
|
||||
router.push(notice.action_route);
|
||||
} else if (notice.action_type === 'external' && notice.action_url) {
|
||||
const target = notice.link_target || '_blank';
|
||||
window.open(notice.action_url, target, 'noopener,noreferrer');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dismiss ──────────────────────────────────────────────────
|
||||
|
||||
async function dismiss () {
|
||||
await noticeStore.dismiss(noticeStore.activeNotice)
|
||||
async function dismiss() {
|
||||
await noticeStore.dismiss(noticeStore.activeNotice);
|
||||
}
|
||||
|
||||
// ── Limpa a CSS var ao desmontar ─────────────────────────────
|
||||
|
||||
onBeforeUnmount(() => setHeight(0))
|
||||
onBeforeUnmount(() => setHeight(0));
|
||||
|
||||
// ── Variantes → estilos ──────────────────────────────────────
|
||||
|
||||
const VARIANT_STYLES = {
|
||||
info: { bg: 'var(--p-blue-600, #2563eb)', icon: 'pi-info-circle' },
|
||||
success: { bg: 'var(--p-green-600, #16a34a)', icon: 'pi-check-circle' },
|
||||
warning: { bg: 'var(--p-amber-500, #f59e0b)', icon: 'pi-exclamation-triangle' },
|
||||
error: { bg: 'var(--p-red-600, #dc2626)', icon: 'pi-times-circle' },
|
||||
}
|
||||
info: { bg: 'var(--p-blue-600, #2563eb)', icon: 'pi-info-circle' },
|
||||
success: { bg: 'var(--p-green-600, #16a34a)', icon: 'pi-check-circle' },
|
||||
warning: { bg: 'var(--p-amber-500, #f59e0b)', icon: 'pi-exclamation-triangle' },
|
||||
error: { bg: 'var(--p-red-600, #dc2626)', icon: 'pi-times-circle' }
|
||||
};
|
||||
|
||||
function variantStyle (variant) {
|
||||
return VARIANT_STYLES[variant] || VARIANT_STYLES.info
|
||||
function variantStyle(variant) {
|
||||
return VARIANT_STYLES[variant] || VARIANT_STYLES.info;
|
||||
}
|
||||
|
||||
const ALIGN_CLASS = {
|
||||
left: 'notice-inner--left',
|
||||
center: 'notice-inner--center',
|
||||
right: 'notice-inner--right',
|
||||
justify: 'notice-inner--justify',
|
||||
}
|
||||
left: 'notice-inner--left',
|
||||
center: 'notice-inner--center',
|
||||
right: 'notice-inner--right',
|
||||
justify: 'notice-inner--justify'
|
||||
};
|
||||
|
||||
function alignClass (align) {
|
||||
return ALIGN_CLASS[align] || ALIGN_CLASS.left
|
||||
function alignClass(align) {
|
||||
return ALIGN_CLASS[align] || ALIGN_CLASS.left;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="notice-slide">
|
||||
<div
|
||||
v-if="visible && noticeStore.activeNotice"
|
||||
ref="bannerEl"
|
||||
class="global-notice-banner"
|
||||
:style="{ background: variantStyle(noticeStore.activeNotice.variant).bg }"
|
||||
role="alert"
|
||||
:aria-label="noticeStore.activeNotice.title || 'Aviso'"
|
||||
>
|
||||
<!-- notice-inner controla só o max-width/padding; o alinhamento fica no notice-group-wrap -->
|
||||
<div class="notice-inner" :class="alignClass(noticeStore.activeNotice.content_align)">
|
||||
<Transition name="notice-slide">
|
||||
<div v-if="visible && noticeStore.activeNotice" ref="bannerEl" class="global-notice-banner" :style="{ background: variantStyle(noticeStore.activeNotice.variant).bg }" role="alert" :aria-label="noticeStore.activeNotice.title || 'Aviso'">
|
||||
<!-- notice-inner controla só o max-width/padding; o alinhamento fica no notice-group-wrap -->
|
||||
<div class="notice-inner" :class="alignClass(noticeStore.activeNotice.content_align)">
|
||||
<!-- Grupo alinhável: ícone + texto + cta -->
|
||||
<div class="notice-group">
|
||||
<i class="notice-icon pi" :class="variantStyle(noticeStore.activeNotice.variant).icon" aria-hidden="true" />
|
||||
|
||||
<!-- Grupo alinhável: ícone + texto + cta -->
|
||||
<div class="notice-group">
|
||||
<i
|
||||
class="notice-icon pi"
|
||||
:class="variantStyle(noticeStore.activeNotice.variant).icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="notice-body">
|
||||
<span v-if="noticeStore.activeNotice.title" class="notice-title">
|
||||
{{ noticeStore.activeNotice.title }}
|
||||
</span>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span class="notice-message" v-html="noticeStore.activeNotice.message" />
|
||||
</div>
|
||||
|
||||
<div class="notice-body">
|
||||
<span v-if="noticeStore.activeNotice.title" class="notice-title">
|
||||
{{ noticeStore.activeNotice.title }}
|
||||
</span>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span class="notice-message" v-html="noticeStore.activeNotice.message" />
|
||||
</div>
|
||||
<button v-if="noticeStore.activeNotice.action_type !== 'none' && noticeStore.activeNotice.action_label" class="notice-cta" type="button" @click="handleCta(noticeStore.activeNotice)">
|
||||
{{ noticeStore.activeNotice.action_label }}
|
||||
<i v-if="noticeStore.activeNotice.action_type === 'external'" class="pi pi-external-link notice-cta__ext" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="noticeStore.activeNotice.action_type !== 'none' && noticeStore.activeNotice.action_label"
|
||||
class="notice-cta"
|
||||
type="button"
|
||||
@click="handleCta(noticeStore.activeNotice)"
|
||||
>
|
||||
{{ noticeStore.activeNotice.action_label }}
|
||||
<i
|
||||
v-if="noticeStore.activeNotice.action_type === 'external'"
|
||||
class="pi pi-external-link notice-cta__ext"
|
||||
/>
|
||||
</button>
|
||||
<!-- Fechar: sempre absoluto no canto direito -->
|
||||
<button v-if="noticeStore.activeNotice.dismissible" class="notice-close" type="button" aria-label="Fechar aviso" @click="dismiss">
|
||||
<i class="pi pi-times" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fechar: sempre absoluto no canto direito -->
|
||||
<button
|
||||
v-if="noticeStore.activeNotice.dismissible"
|
||||
class="notice-close"
|
||||
type="button"
|
||||
aria-label="Fechar aviso"
|
||||
@click="dismiss"
|
||||
>
|
||||
<i class="pi pi-times" />
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.global-notice-banner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 101; /* acima da topbar (z-index 100) */
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 101; /* acima da topbar (z-index 100) */
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.notice-inner {
|
||||
position: relative; /* âncora do botão fechar absoluto */
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2.5rem 0 1rem; /* padding-right reserva espaço pro fechar */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
position: relative; /* âncora do botão fechar absoluto */
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2.5rem 0 1rem; /* padding-right reserva espaço pro fechar */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
/* ── Grupo alinhável (ícone + texto + cta) ───────────────── */
|
||||
.notice-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
/* ── Alinhamento: muda só o .notice-group dentro do .notice-inner ── */
|
||||
.notice-inner--left { justify-content: flex-start; }
|
||||
.notice-inner--center { justify-content: center; }
|
||||
.notice-inner--right { justify-content: flex-end; }
|
||||
.notice-inner--justify { justify-content: flex-start; }
|
||||
.notice-inner--left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.notice-inner--center {
|
||||
justify-content: center;
|
||||
}
|
||||
.notice-inner--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.notice-inner--justify {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* justify: grupo expande para preencher toda a largura */
|
||||
.notice-inner--justify .notice-group { flex: 1; }
|
||||
.notice-inner--justify .notice-body { flex: 1; }
|
||||
.notice-inner--justify .notice-group {
|
||||
flex: 1;
|
||||
}
|
||||
.notice-inner--justify .notice-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── Ícone ───────────────────────────────────────────────── */
|
||||
.notice-icon {
|
||||
font-size: 1rem;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
flex-shrink: 0;
|
||||
font-size: 1rem;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Corpo de texto ──────────────────────────────────────── */
|
||||
.notice-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.85rem;
|
||||
color: #fff;
|
||||
line-height: 1.45;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.85rem;
|
||||
color: #fff;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notice-message {
|
||||
opacity: 0.92;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
/* Links dentro do HTML do message */
|
||||
.notice-message :deep(a) {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── CTA button ──────────────────────────────────────────── */
|
||||
.notice-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.55);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.55);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background 0.15s,
|
||||
border-color 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-cta:hover {
|
||||
background: rgba(255, 255, 255, 0.28);
|
||||
border-color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(255, 255, 255, 0.28);
|
||||
border-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.notice-cta__ext {
|
||||
font-size: 0.65rem;
|
||||
opacity: 0.8;
|
||||
font-size: 0.65rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ── Fechar: sempre ancorado no canto direito ────────────── */
|
||||
.notice-close {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
}
|
||||
|
||||
.notice-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ── Transição slide-down ────────────────────────────────── */
|
||||
.notice-slide-enter-active {
|
||||
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.22s ease;
|
||||
transition:
|
||||
transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity 0.22s ease;
|
||||
}
|
||||
.notice-slide-leave-active {
|
||||
transition: transform 0.22s ease, opacity 0.2s ease;
|
||||
transition:
|
||||
transform 0.22s ease,
|
||||
opacity 0.2s ease;
|
||||
}
|
||||
.notice-slide-enter-from,
|
||||
.notice-slide-leave-to {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Dark mode: sem mudança — cores via variante ─────────── */
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
// Serviço central de acesso ao Supabase para Global Notices
|
||||
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
|
||||
// ── Leitura ────────────────────────────────────────────────────
|
||||
|
||||
@@ -25,107 +25,92 @@ import { supabase } from '@/lib/supabase/client'
|
||||
* A filtragem por role/context é feita no cliente (noticeStore)
|
||||
* para evitar lógica de array no PostgREST.
|
||||
*/
|
||||
export async function fetchActiveNotices () {
|
||||
const now = new Date().toISOString()
|
||||
export async function fetchActiveNotices() {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('global_notices')
|
||||
.select('*')
|
||||
.eq('is_active', true)
|
||||
.or(`starts_at.is.null,starts_at.lte.${now}`)
|
||||
.or(`ends_at.is.null,ends_at.gte.${now}`)
|
||||
.order('priority', { ascending: false })
|
||||
const { data, error } = await supabase.from('global_notices').select('*').eq('is_active', true).or(`starts_at.is.null,starts_at.lte.${now}`).or(`ends_at.is.null,ends_at.gte.${now}`).order('priority', { ascending: false });
|
||||
|
||||
if (error) throw error
|
||||
return data || []
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca todos os notices (sem filtro de ativo) — para o painel admin.
|
||||
*/
|
||||
export async function fetchAllNotices () {
|
||||
const { data, error } = await supabase
|
||||
.from('global_notices')
|
||||
.select('*')
|
||||
.order('priority', { ascending: false })
|
||||
.order('created_at', { ascending: false })
|
||||
export async function fetchAllNotices() {
|
||||
const { data, error } = await supabase.from('global_notices').select('*').order('priority', { ascending: false }).order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error
|
||||
return data || []
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
}
|
||||
|
||||
// ── Tracking ───────────────────────────────────────────────────
|
||||
|
||||
export async function trackView (noticeId) {
|
||||
try {
|
||||
await supabase.rpc('notice_track_view', { p_notice_id: noticeId })
|
||||
} catch { /* silencioso — tracking não deve quebrar o app */ }
|
||||
export async function trackView(noticeId) {
|
||||
try {
|
||||
await supabase.rpc('notice_track_view', { p_notice_id: noticeId });
|
||||
} catch {
|
||||
/* silencioso — tracking não deve quebrar o app */
|
||||
}
|
||||
}
|
||||
|
||||
export async function trackClick (noticeId) {
|
||||
try {
|
||||
await supabase.rpc('notice_track_click', { p_notice_id: noticeId })
|
||||
} catch {}
|
||||
export async function trackClick(noticeId) {
|
||||
try {
|
||||
await supabase.rpc('notice_track_click', { p_notice_id: noticeId });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ── Dismiss scope: 'user' (persiste no banco) ─────────────────
|
||||
|
||||
export async function saveDismissal (noticeId, version) {
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user?.id) return
|
||||
export async function saveDismissal(noticeId, version) {
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user?.id) return;
|
||||
|
||||
await supabase
|
||||
.from('notice_dismissals')
|
||||
.upsert({ notice_id: noticeId, user_id: user.id, version }, { onConflict: 'notice_id,user_id' })
|
||||
await supabase.from('notice_dismissals').upsert({ notice_id: noticeId, user_id: user.id, version }, { onConflict: 'notice_id,user_id' });
|
||||
}
|
||||
|
||||
export async function loadUserDismissals () {
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user?.id) return []
|
||||
export async function loadUserDismissals() {
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user?.id) return [];
|
||||
|
||||
const { data } = await supabase
|
||||
.from('notice_dismissals')
|
||||
.select('notice_id, version')
|
||||
.eq('user_id', user.id)
|
||||
const { data } = await supabase.from('notice_dismissals').select('notice_id, version').eq('user_id', user.id);
|
||||
|
||||
return data || []
|
||||
return data || [];
|
||||
}
|
||||
|
||||
// ── Admin CRUD ─────────────────────────────────────────────────
|
||||
|
||||
export async function createNotice (payload) {
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
const { data, error } = await supabase
|
||||
.from('global_notices')
|
||||
.insert({ ...payload, created_by: user?.id })
|
||||
.select()
|
||||
.single()
|
||||
export async function createNotice(payload) {
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
const { data, error } = await supabase
|
||||
.from('global_notices')
|
||||
.insert({ ...payload, created_by: user?.id })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
if (error) throw error;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateNotice (id, payload) {
|
||||
const { data, error } = await supabase
|
||||
.from('global_notices')
|
||||
.update(payload)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single()
|
||||
export async function updateNotice(id, payload) {
|
||||
const { data, error } = await supabase.from('global_notices').update(payload).eq('id', id).select().single();
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
if (error) throw error;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteNotice (id) {
|
||||
const { error } = await supabase
|
||||
.from('global_notices')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
export async function deleteNotice(id) {
|
||||
const { error } = await supabase.from('global_notices').delete().eq('id', id);
|
||||
|
||||
if (error) throw error
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
export async function toggleNoticeActive (id, isActive) {
|
||||
return updateNotice(id, { is_active: isActive })
|
||||
export async function toggleNoticeActive(id, isActive) {
|
||||
return updateNotice(id, { is_active: isActive });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user