first commit
This commit is contained in:
@@ -0,0 +1,637 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
import Toolbar from 'primevue/toolbar'
|
||||
import Button from 'primevue/button'
|
||||
import DataTable from 'primevue/datatable'
|
||||
import Column from 'primevue/column'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Textarea from 'primevue/textarea'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import Toast from 'primevue/toast'
|
||||
import ConfirmDialog from 'primevue/confirmdialog'
|
||||
import Popover from 'primevue/popover'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
|
||||
import Avatar from 'primevue/avatar'
|
||||
import AvatarGroup from 'primevue/avatargroup'
|
||||
import Divider from 'primevue/divider'
|
||||
import Badge from 'primevue/badge'
|
||||
|
||||
const billingInterval = ref('month') // 'month' | 'year'
|
||||
|
||||
function priceCentsFor (p, interval) {
|
||||
return interval === 'year' ? p.yearly_cents : p.monthly_cents
|
||||
}
|
||||
|
||||
|
||||
// busca com FloatLabel (padrão)
|
||||
import FloatLabel from 'primevue/floatlabel'
|
||||
import IconField from 'primevue/iconfield'
|
||||
import InputIcon from 'primevue/inputicon'
|
||||
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref([])
|
||||
const q = ref('')
|
||||
|
||||
const showDlg = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const showBulletDlg = ref(false)
|
||||
const bulletSaving = ref(false)
|
||||
const bulletIsEdit = ref(false)
|
||||
|
||||
const selected = ref(null)
|
||||
|
||||
const form = ref({
|
||||
plan_id: null,
|
||||
public_name: '',
|
||||
public_description: '',
|
||||
badge: '',
|
||||
is_featured: false,
|
||||
is_visible: true,
|
||||
sort_order: 0
|
||||
})
|
||||
|
||||
const bullets = ref([])
|
||||
const bulletForm = ref({
|
||||
id: null,
|
||||
text: '',
|
||||
sort_order: 0,
|
||||
highlight: false
|
||||
})
|
||||
|
||||
/* popover bullets (na tabela) */
|
||||
const bulletsPop = ref(null)
|
||||
const popPlanTitle = ref('')
|
||||
const popBullets = ref([])
|
||||
|
||||
function openBulletsPopover (event, row) {
|
||||
popPlanTitle.value = row.public_name || row.plan_name || row.plan_key || 'Benefícios'
|
||||
popBullets.value = Array.isArray(row.bullets) ? row.bullets : []
|
||||
bulletsPop.value?.toggle(event)
|
||||
}
|
||||
|
||||
function formatBRLFromCents (cents) {
|
||||
if (cents == null) return '—'
|
||||
const v = Number(cents) / 100
|
||||
return v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
|
||||
}
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
const term = String(q.value || '').trim().toLowerCase()
|
||||
if (!term) return rows.value
|
||||
return rows.value.filter(r => {
|
||||
const a = String(r.public_name || '').toLowerCase()
|
||||
const b = String(r.plan_key || '').toLowerCase()
|
||||
const c = String(r.plan_name || '').toLowerCase()
|
||||
return a.includes(term) || b.includes(term) || c.includes(term)
|
||||
})
|
||||
})
|
||||
|
||||
const previewPlans = computed(() => {
|
||||
// preview: só visíveis, ordenados. Featured primeiro dentro da mesma ordem.
|
||||
const list = (rows.value || [])
|
||||
.filter(r => r.is_visible !== false)
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
const ao = Number(a.sort_order ?? 0)
|
||||
const bo = Number(b.sort_order ?? 0)
|
||||
if (ao !== bo) return ao - bo
|
||||
const af = a.is_featured ? 0 : 1
|
||||
const bf = b.is_featured ? 0 : 1
|
||||
if (af !== bf) return af - bf
|
||||
return String(a.plan_key || '').localeCompare(String(b.plan_key || ''))
|
||||
})
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
async function fetchAll () {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('v_public_pricing')
|
||||
.select('*')
|
||||
|
||||
if (error) throw error
|
||||
rows.value = data || []
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 4500 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openEdit (row) {
|
||||
selected.value = row
|
||||
form.value = {
|
||||
plan_id: row.plan_id,
|
||||
public_name: row.public_name || row.plan_name || row.plan_key || '',
|
||||
public_description: row.public_description || '',
|
||||
badge: row.badge || '',
|
||||
is_featured: !!row.is_featured,
|
||||
is_visible: row.is_visible !== false,
|
||||
sort_order: Number(row.sort_order ?? 0)
|
||||
}
|
||||
|
||||
await fetchBullets(row.plan_id)
|
||||
showDlg.value = true
|
||||
}
|
||||
|
||||
async function fetchBullets (planId) {
|
||||
const { data, error } = await supabase
|
||||
.from('plan_public_bullets')
|
||||
.select('*')
|
||||
.eq('plan_id', planId)
|
||||
.order('sort_order', { ascending: true })
|
||||
.order('created_at', { ascending: true })
|
||||
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 4500 })
|
||||
bullets.value = []
|
||||
return
|
||||
}
|
||||
|
||||
bullets.value = data || []
|
||||
}
|
||||
|
||||
function validate () {
|
||||
const name = String(form.value.public_name || '').trim()
|
||||
if (!name) {
|
||||
toast.add({ severity: 'warn', summary: 'Atenção', detail: 'Informe o nome público do plano.', life: 3000 })
|
||||
return false
|
||||
}
|
||||
|
||||
form.value.public_name = name
|
||||
form.value.public_description = String(form.value.public_description || '').trim()
|
||||
form.value.badge = String(form.value.badge || '').trim() || null
|
||||
form.value.sort_order = Number(form.value.sort_order ?? 0)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function save () {
|
||||
if (!validate()) return
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
const payload = { ...form.value }
|
||||
const { error } = await supabase
|
||||
.from('plan_public')
|
||||
.upsert(payload, { onConflict: 'plan_id' })
|
||||
|
||||
if (error) throw error
|
||||
|
||||
toast.add({ severity: 'success', summary: 'Ok', detail: 'Vitrine atualizada.', life: 2500 })
|
||||
showDlg.value = false
|
||||
await fetchAll()
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 4500 })
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- bullets (CRUD no dialog) ---------- */
|
||||
|
||||
function openBulletCreate () {
|
||||
bulletIsEdit.value = false
|
||||
bulletForm.value = { id: null, text: '', sort_order: (bullets.value?.length || 0) + 1, highlight: false }
|
||||
showBulletDlg.value = true
|
||||
}
|
||||
|
||||
function openBulletEdit (row) {
|
||||
bulletIsEdit.value = true
|
||||
bulletForm.value = {
|
||||
id: row.id,
|
||||
text: row.text ?? '',
|
||||
sort_order: Number(row.sort_order ?? 0),
|
||||
highlight: !!row.highlight
|
||||
}
|
||||
showBulletDlg.value = true
|
||||
}
|
||||
|
||||
function validateBullet () {
|
||||
const t = String(bulletForm.value.text || '').trim()
|
||||
if (!t) {
|
||||
toast.add({ severity: 'warn', summary: 'Atenção', detail: 'Informe o texto do benefício.', life: 3000 })
|
||||
return false
|
||||
}
|
||||
bulletForm.value.text = t
|
||||
bulletForm.value.sort_order = Number(bulletForm.value.sort_order ?? 0)
|
||||
return true
|
||||
}
|
||||
|
||||
async function saveBullet () {
|
||||
if (!selected.value?.plan_id) return
|
||||
if (!validateBullet()) return
|
||||
bulletSaving.value = true
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
plan_id: selected.value.plan_id,
|
||||
text: bulletForm.value.text,
|
||||
sort_order: bulletForm.value.sort_order,
|
||||
highlight: !!bulletForm.value.highlight
|
||||
}
|
||||
|
||||
if (bulletIsEdit.value) {
|
||||
const { error } = await supabase
|
||||
.from('plan_public_bullets')
|
||||
.update(payload)
|
||||
.eq('id', bulletForm.value.id)
|
||||
|
||||
if (error) throw error
|
||||
toast.add({ severity: 'success', summary: 'Ok', detail: 'Benefício atualizado.', life: 2200 })
|
||||
} else {
|
||||
const { error } = await supabase
|
||||
.from('plan_public_bullets')
|
||||
.insert(payload)
|
||||
|
||||
if (error) throw error
|
||||
toast.add({ severity: 'success', summary: 'Ok', detail: 'Benefício adicionado.', life: 2200 })
|
||||
}
|
||||
|
||||
showBulletDlg.value = false
|
||||
await fetchBullets(selected.value.plan_id)
|
||||
await fetchAll()
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 4500 })
|
||||
} finally {
|
||||
bulletSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function askDeleteBullet (row) {
|
||||
confirm.require({
|
||||
message: 'Excluir este benefício?',
|
||||
header: 'Confirmar exclusão',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: () => doDeleteBullet(row)
|
||||
})
|
||||
}
|
||||
|
||||
async function doDeleteBullet (row) {
|
||||
const { error } = await supabase
|
||||
.from('plan_public_bullets')
|
||||
.delete()
|
||||
.eq('id', row.id)
|
||||
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 4500 })
|
||||
return
|
||||
}
|
||||
|
||||
toast.add({ severity: 'success', summary: 'Ok', detail: 'Benefício removido.', life: 2200 })
|
||||
await fetchBullets(selected.value.plan_id)
|
||||
await fetchAll()
|
||||
}
|
||||
|
||||
onMounted(fetchAll)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
|
||||
<div class="p-4">
|
||||
<Toolbar class="mb-4">
|
||||
<template #start>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-xl font-semibold leading-none">Vitrine de Planos</div>
|
||||
<small class="text-color-secondary mt-1">
|
||||
Configure como os planos aparecem na página pública (nome, descrição, badge, ordem e benefícios).
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #end>
|
||||
<div class="flex items-center gap-2 w-full md:w-auto">
|
||||
<div class="w-full md:w-80">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<IconField class="w-full">
|
||||
<InputIcon class="pi pi-search" />
|
||||
<InputText v-model="q" id="plans_public_search" class="w-full pr-10" variant="filled" />
|
||||
</IconField>
|
||||
<label for="plans_public_search">Buscar plano</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<Button label="Recarregar" icon="pi pi-refresh" severity="secondary" outlined :loading="loading" @click="fetchAll" />
|
||||
</div>
|
||||
</template>
|
||||
</Toolbar>
|
||||
|
||||
<!-- Popover global (reutilizado) -->
|
||||
<Popover ref="bulletsPop">
|
||||
<div class="w-[340px] max-w-[80vw]">
|
||||
<div class="text-sm font-semibold mb-2">{{ popPlanTitle }}</div>
|
||||
|
||||
<div v-if="!popBullets?.length" class="text-sm text-color-secondary">
|
||||
Nenhum benefício configurado.
|
||||
</div>
|
||||
|
||||
<ul v-else class="m-0 pl-4 space-y-2">
|
||||
<li v-for="b in popBullets" :key="b.id" class="text-sm leading-snug">
|
||||
<span :class="b.highlight ? 'font-semibold' : ''">
|
||||
{{ b.text }}
|
||||
</span>
|
||||
<small v-if="b.highlight" class="ml-2 text-color-secondary">(destaque)</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<DataTable :value="filteredRows" dataKey="plan_id" :loading="loading" stripedRows responsiveLayout="scroll">
|
||||
<Column header="Plano" style="min-width: 18rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold">{{ data.public_name || data.plan_name || data.plan_key }}</span>
|
||||
<small class="text-color-secondary">
|
||||
{{ data.plan_key }} • {{ data.plan_name || '—' }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Mensal" style="width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<span class="font-medium">{{ formatBRLFromCents(data.monthly_cents) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Anual" style="width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<span class="font-medium">{{ formatBRLFromCents(data.yearly_cents) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="badge" header="Badge" style="min-width: 12rem" />
|
||||
|
||||
<Column header="Visível" style="width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<span>{{ data.is_visible ? 'Sim' : 'Não' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Destaque" style="width: 9rem">
|
||||
<template #body="{ data }">
|
||||
<span>{{ data.is_featured ? 'Sim' : 'Não' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="sort_order" header="Ordem" style="width: 8rem" />
|
||||
|
||||
<Column header="Ações" style="width: 14rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex gap-2 justify-end">
|
||||
<!-- Popover bullets: ícone + quantidade -->
|
||||
<Button
|
||||
severity="secondary"
|
||||
outlined
|
||||
size="small"
|
||||
@click="(e) => openBulletsPopover(e, data)"
|
||||
>
|
||||
<i class="pi pi-list mr-2" />
|
||||
<span class="font-medium">{{ data.bullets?.length || 0 }}</span>
|
||||
</Button>
|
||||
|
||||
<Button icon="pi pi-pencil" severity="secondary" outlined size="small" @click="openEdit(data)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<!-- PREVIEW PÚBLICO (estilo PrimeBlocks) -->
|
||||
<div class="mt-10">
|
||||
<div class="rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] p-6 md:p-10 overflow-hidden">
|
||||
<!-- topo "happy customers" + título -->
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<AvatarGroup>
|
||||
<Avatar image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-m-1.png" shape="circle" />
|
||||
<Avatar image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-f-21.png" shape="circle" />
|
||||
<Avatar image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-f-1.png" shape="circle" />
|
||||
<Avatar image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-m-3.png" shape="circle" />
|
||||
</AvatarGroup>
|
||||
|
||||
<Divider layout="vertical" />
|
||||
|
||||
<span class="text-sm text-color-secondary font-medium">Happy Customers</span>
|
||||
</div>
|
||||
|
||||
<h2 class="text-3xl md:text-5xl font-semibold leading-tight">
|
||||
Get a plan and<br />
|
||||
increase your efficiency
|
||||
</h2>
|
||||
|
||||
<p class="text-color-secondary mt-3 max-w-2xl">
|
||||
Optimize your workflow and boost productivity by choosing the right plan tailored to your business needs.
|
||||
</p>
|
||||
|
||||
<!-- Toggle Monthly / Yearly -->
|
||||
<div class="mt-6 inline-flex items-center rounded-xl border border-[var(--surface-border)] bg-[var(--surface-50)] p-1">
|
||||
<Button
|
||||
label="Mensal"
|
||||
size="small"
|
||||
:severity="billingInterval === 'month' ? 'success' : 'secondary'"
|
||||
:outlined="billingInterval !== 'month'"
|
||||
@click="billingInterval = 'month'"
|
||||
/>
|
||||
<Button
|
||||
label="Anual"
|
||||
size="small"
|
||||
:severity="billingInterval === 'year' ? 'success' : 'secondary'"
|
||||
:outlined="billingInterval !== 'year'"
|
||||
class="ml-1"
|
||||
@click="billingInterval = 'year'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- cards -->
|
||||
<div class="mt-10 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div
|
||||
v-for="p in previewPlans"
|
||||
:key="p.plan_id"
|
||||
:class="[
|
||||
'relative rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] overflow-hidden',
|
||||
'shadow-sm transition-transform',
|
||||
p.is_featured ? 'md:-translate-y-2 md:scale-[1.02] ring-1 ring-emerald-500/25' : ''
|
||||
]"
|
||||
>
|
||||
<!-- “franja”/topo decorativo (bem leve) -->
|
||||
<div class="h-3 w-full opacity-40 bg-[var(--surface-100)]" />
|
||||
|
||||
<div class="p-6">
|
||||
<!-- badge do plano -->
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Badge
|
||||
:value="p.badge || (p.is_featured ? 'Popular' : '')"
|
||||
:severity="p.is_featured ? 'success' : 'secondary'"
|
||||
v-if="p.badge || p.is_featured"
|
||||
/>
|
||||
<span class="text-xs text-color-secondary">{{ p.plan_key }}</span>
|
||||
</div>
|
||||
|
||||
<!-- preço -->
|
||||
<div class="mt-4 text-4xl font-semibold leading-none">
|
||||
{{ formatBRLFromCents(priceCentsFor(p, billingInterval)) }}
|
||||
</div>
|
||||
|
||||
<p class="text-color-secondary mt-3 min-h-[44px]">
|
||||
{{ p.public_description || '—' }}
|
||||
</p>
|
||||
|
||||
<Button
|
||||
class="mt-5 w-full"
|
||||
:label="p.is_featured ? 'Começar agora!' : 'Selecionar este'"
|
||||
:severity="p.is_featured ? 'success' : 'secondary'"
|
||||
:outlined="!p.is_featured"
|
||||
/>
|
||||
|
||||
<!-- divisória pontilhada “conceitual” -->
|
||||
<div class="mt-6">
|
||||
<Divider type="dashed" />
|
||||
</div>
|
||||
|
||||
<!-- bullets -->
|
||||
<ul v-if="p.bullets?.length" class="mt-4 space-y-2">
|
||||
<li v-for="b in p.bullets" :key="b.id" class="flex items-start gap-2">
|
||||
<i class="pi pi-check mt-1 text-sm"></i>
|
||||
<span :class="['text-sm leading-snug', b.highlight ? 'font-semibold' : '']">
|
||||
{{ b.text }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-else class="mt-4 text-sm text-color-secondary">
|
||||
Nenhum benefício configurado.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Dialog principal -->
|
||||
<Dialog v-model:visible="showDlg" modal header="Editar vitrine" :style="{ width: '820px' }">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block mb-2">Nome público</label>
|
||||
<InputText v-model="form.public_name" class="w-full" placeholder="ex.: Profissional" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block mb-2">Descrição pública</label>
|
||||
<Textarea
|
||||
v-model="form.public_description"
|
||||
class="w-full"
|
||||
:autoResize="true"
|
||||
rows="3"
|
||||
placeholder="Uma frase curta e clara..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block mb-2">Badge</label>
|
||||
<InputText v-model="form.badge" class="w-full" placeholder="ex.: Mais popular (opcional)" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block mb-2">Ordem</label>
|
||||
<InputNumber v-model="form.sort_order" class="w-full" inputClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 pt-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox v-model="form.is_visible" :binary="true" />
|
||||
<label>Visível no público</label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox v-model="form.is_featured" :binary="true" />
|
||||
<label>Destaque</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- bullets -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="font-semibold">Benefícios (bullets)</div>
|
||||
<Button label="Adicionar" icon="pi pi-plus" size="small" @click="openBulletCreate" />
|
||||
</div>
|
||||
|
||||
<DataTable :value="bullets" dataKey="id" stripedRows responsiveLayout="scroll">
|
||||
<Column field="text" header="Texto" />
|
||||
<Column field="sort_order" header="Ordem" style="width: 7rem" />
|
||||
<Column header="Destaque" style="width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<span>{{ data.highlight ? 'Sim' : 'Não' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="Ações" style="width: 9rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-pencil" severity="secondary" outlined size="small" @click="openBulletEdit(data)" />
|
||||
<Button icon="pi pi-trash" severity="danger" outlined size="small" @click="askDeleteBullet(data)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button label="Cancelar" severity="secondary" outlined @click="showDlg = false" />
|
||||
<Button label="Salvar" icon="pi pi-check" :loading="saving" @click="save" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Dialog bullet -->
|
||||
<Dialog v-model:visible="showBulletDlg" modal :header="bulletIsEdit ? 'Editar benefício' : 'Novo benefício'" :style="{ width: '560px' }">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block mb-2">Texto</label>
|
||||
<Textarea
|
||||
v-model="bulletForm.text"
|
||||
class="w-full"
|
||||
:autoResize="true"
|
||||
rows="3"
|
||||
placeholder="ex.: Agendamento online com página pública"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block mb-2">Ordem</label>
|
||||
<InputNumber v-model="bulletForm.sort_order" class="w-full" inputClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 pt-7">
|
||||
<Checkbox v-model="bulletForm.highlight" :binary="true" />
|
||||
<label>Destaque</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button label="Cancelar" severity="secondary" outlined @click="showBulletDlg = false" />
|
||||
<Button :label="bulletIsEdit ? 'Salvar' : 'Criar'" icon="pi pi-check" :loading="bulletSaving" @click="saveBullet" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user