394 lines
16 KiB
JavaScript
394 lines
16 KiB
JavaScript
// ── CLUBE67 PREMIUM INTERACTIVE SCRIPT ──
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Initialize Lucide Icons
|
|
lucide.createIcons();
|
|
|
|
// ── STATE MANAGEMENT ──
|
|
let activeTab = 'catalog';
|
|
let benefits = [];
|
|
let activeVoucher = null;
|
|
let countdownInterval = null;
|
|
let timerTotalSeconds = 300; // 5 minutes
|
|
let timerRemainingSeconds = 300;
|
|
|
|
// Premium Mock Data in case backend returns empty/offline to guarantee flawless display
|
|
const mockBenefits = [
|
|
{ id: 1, title: "Consulta + Limpeza Geral", description: "Ganhe 50% de desconto na primeira consulta preventiva e profilaxia.", category: "odontologia", partner: "ScoreOdonto MS", icon: "🦷", rating: "4.9", discount: "50% OFF" },
|
|
{ id: 2, title: "Checkout Cardiológico Completo", description: "Desconto especial de 30% em exames de eletrocardiograma e ecocardiograma.", category: "saude", partner: "CardioClinic CG", icon: "🩺", rating: "4.8", discount: "30% OFF" },
|
|
{ id: 3, title: "Menu Degustação Premium (Casal)", description: "Compre um menu degustação de 5 etapas e ganhe uma garrafa de vinho importado.", category: "gastronomia", partner: "Le Jardin Bistrô", icon: "🍔", rating: "5.0", discount: "Vinho Cortesia" },
|
|
{ id: 4, title: "Day Use Completo no Eco Resort", description: "Acesso total às piscinas naturais, trilhas e almoço pantaneiro incluso com 25% OFF.", category: "lazer", partner: "Bonito Eco Resort", icon: "🌴", rating: "4.7", discount: "25% OFF" },
|
|
{ id: 5, title: "Fórmulas Manipuladas e Vitaminas", description: "Desconto exclusivo de 20% em qualquer fórmula médica manipulada.", category: "farmacia", partner: "Fórmula Ativa", icon: "💊", rating: "4.9", discount: "20% OFF" },
|
|
{ id: 6, title: "MBA Executivo & Especializações", description: "Isenção na taxa de matrícula e 35% de desconto nas mensalidades do curso.", category: "educacao", partner: "Unigran Capital", icon: "📚", rating: "4.6", discount: "35% OFF" }
|
|
];
|
|
|
|
// Analytics Dashboard Data
|
|
const analyticsData = {
|
|
members: 1248,
|
|
redemptions: 384,
|
|
savings: 14820,
|
|
goalPercentage: 96,
|
|
weeklyDistribution: {
|
|
seg: 25,
|
|
ter: 45,
|
|
qua: 70,
|
|
qui: 55,
|
|
sex: 85,
|
|
sab: 100,
|
|
dom: 35
|
|
}
|
|
};
|
|
|
|
// ── DOM ELEMENTS ──
|
|
const navTabs = {
|
|
catalog: document.getElementById('tab-catalog'),
|
|
redeem: document.getElementById('tab-redeem'),
|
|
analytics: document.getElementById('tab-analytics')
|
|
};
|
|
|
|
const sections = {
|
|
catalog: document.getElementById('section-catalog'),
|
|
redeem: document.getElementById('section-redeem'),
|
|
analytics: document.getElementById('section-analytics')
|
|
};
|
|
|
|
const searchInput = document.getElementById('search-input');
|
|
const categoryFilters = document.getElementById('category-filters');
|
|
const benefitsGrid = document.getElementById('benefits-grid');
|
|
|
|
// Redeem tab elements
|
|
const redeemCardEmpty = document.getElementById('redeem-card-empty');
|
|
const redeemCardActive = document.getElementById('redeem-card-active');
|
|
const voucherIcon = document.getElementById('voucher-icon');
|
|
const voucherTitle = document.getElementById('voucher-title');
|
|
const voucherPartner = document.getElementById('voucher-partner');
|
|
const voucherToken = document.getElementById('voucher-token');
|
|
const qrCodeImg = document.getElementById('qr-code-img');
|
|
const timerText = document.getElementById('timer-text');
|
|
const timerBar = document.getElementById('timer-bar');
|
|
const btnValidate = document.getElementById('btn-validate');
|
|
const btnCancel = document.getElementById('btn-cancel');
|
|
|
|
// Analytics elements
|
|
const statMembers = document.getElementById('stat-members');
|
|
const statRedemptions = document.getElementById('stat-redemptions');
|
|
const statSavings = document.getElementById('stat-savings');
|
|
const radialProgressBar = document.getElementById('radial-progress-bar');
|
|
const radialPercentage = document.getElementById('radial-percentage');
|
|
|
|
const barSeg = document.getElementById('bar-seg');
|
|
const barTer = document.getElementById('bar-ter');
|
|
const barQua = document.getElementById('bar-qua');
|
|
const barQui = document.getElementById('bar-qui');
|
|
const barSex = document.getElementById('bar-sex');
|
|
const barSab = document.getElementById('bar-sab');
|
|
const barDom = document.getElementById('bar-dom');
|
|
|
|
// ── NAVIGATION LOGIC ──
|
|
function switchTab(tabId) {
|
|
if (activeTab === tabId) return;
|
|
|
|
// Update tabs active state
|
|
Object.keys(navTabs).forEach(key => {
|
|
if (key === tabId) {
|
|
navTabs[key].classList.add('active');
|
|
} else {
|
|
navTabs[key].classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Hide old section, show new section
|
|
Object.keys(sections).forEach(key => {
|
|
if (key === tabId) {
|
|
sections[key].classList.remove('hidden');
|
|
} else {
|
|
sections[key].classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
activeTab = tabId;
|
|
|
|
// Perform specific triggers per tab
|
|
if (tabId === 'analytics') {
|
|
loadAnalyticsCharts();
|
|
}
|
|
}
|
|
|
|
Object.keys(navTabs).forEach(key => {
|
|
navTabs[key].addEventListener('click', () => switchTab(key));
|
|
});
|
|
|
|
// ── BENEFIT FETCHING & RENDERING ──
|
|
async function loadBenefits() {
|
|
try {
|
|
// Sincronized backend API consume
|
|
const response = await fetch('/api/benefits');
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
benefits = data && data.length > 0 ? data : mockBenefits;
|
|
} else {
|
|
benefits = mockBenefits;
|
|
}
|
|
} catch (err) {
|
|
console.warn('[Clube67] API offline, running with premium fallback simulation.');
|
|
benefits = mockBenefits;
|
|
}
|
|
|
|
renderBenefits(benefits);
|
|
}
|
|
|
|
function renderBenefits(list) {
|
|
const loadingEl = document.getElementById('catalog-loading');
|
|
if (loadingEl) loadingEl.remove();
|
|
|
|
benefitsGrid.innerHTML = '';
|
|
if (list.length === 0) {
|
|
benefitsGrid.innerHTML = `
|
|
<div class="col-span-full py-16 text-center text-slate-500">
|
|
<i data-lucide="frown" class="w-10 h-10 mx-auto mb-3"></i>
|
|
<p class="text-sm">Nenhum benefício encontrado para esta categoria ou busca.</p>
|
|
</div>
|
|
`;
|
|
lucide.createIcons();
|
|
return;
|
|
}
|
|
|
|
list.forEach(benefit => {
|
|
const card = document.createElement('div');
|
|
card.className = 'benefit-card';
|
|
|
|
// Mouse moves 3D lighting effect
|
|
card.addEventListener('mousemove', e => {
|
|
const rect = card.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
card.style.setProperty('--mouse-x', `${x}px`);
|
|
card.style.setProperty('--mouse-y', `${y}px`);
|
|
});
|
|
|
|
card.innerHTML = `
|
|
<div class="absolute top-4 right-4 bg-indigo-500/10 border border-indigo-500/20 text-indigo-300 text-[10px] font-bold uppercase tracking-widest px-3 py-1 rounded-full">
|
|
${benefit.discount || 'Desconto'}
|
|
</div>
|
|
|
|
<div class="flex items-center gap-4 mb-5">
|
|
<div class="w-12 h-12 rounded-2xl bg-indigo-600/10 border border-indigo-500/10 flex items-center justify-center text-2xl shadow-inner">
|
|
${benefit.icon || '🎁'}
|
|
</div>
|
|
<div>
|
|
<span class="text-[9px] font-bold tracking-widest text-slate-500 uppercase">${benefit.partner || 'Estabelecimento'}</span>
|
|
<h4 class="font-display font-bold text-base text-white leading-snug line-clamp-1 mt-0.5">${benefit.title}</h4>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-slate-400 text-xs leading-relaxed mb-6 min-h-[48px] line-clamp-3">${benefit.description}</p>
|
|
|
|
<div class="flex items-center justify-between border-t border-slate-800/80 pt-4">
|
|
<div class="flex items-center gap-1.5 text-xs font-semibold text-amber-400">
|
|
<i data-lucide="star" class="w-3.5 h-3.5 fill-amber-400"></i>
|
|
<span>${benefit.rating || '4.8'}</span>
|
|
</div>
|
|
<button class="btn-redeem-trigger btn-primary px-4 py-2 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-all duration-300" data-id="${benefit.id}">
|
|
Resgatar Voucher
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
benefitsGrid.appendChild(card);
|
|
});
|
|
|
|
// Add event listeners to voucher buttons
|
|
document.querySelectorAll('.btn-redeem-trigger').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const id = parseInt(btn.getAttribute('data-id'), 10);
|
|
const selected = benefits.find(b => b.id === id);
|
|
if (selected) {
|
|
triggerVoucherRedemption(selected);
|
|
}
|
|
});
|
|
});
|
|
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// ── SEARCH & FILTERING ──
|
|
let activeCategory = 'all';
|
|
|
|
function filterBenefits() {
|
|
const query = searchInput.value.toLowerCase().trim();
|
|
const filtered = benefits.filter(b => {
|
|
const matchesCategory = activeCategory === 'all' || b.category === activeCategory;
|
|
const matchesSearch = b.title.toLowerCase().includes(query) ||
|
|
b.description.toLowerCase().includes(query) ||
|
|
(b.partner && b.partner.toLowerCase().includes(query));
|
|
return matchesCategory && matchesSearch;
|
|
});
|
|
renderBenefits(filtered);
|
|
}
|
|
|
|
searchInput.addEventListener('input', filterBenefits);
|
|
|
|
categoryFilters.querySelectorAll('.filter-pill').forEach(pill => {
|
|
pill.addEventListener('click', () => {
|
|
categoryFilters.querySelector('.filter-pill.active').classList.remove('active');
|
|
pill.classList.add('active');
|
|
activeCategory = pill.getAttribute('data-category');
|
|
filterBenefits();
|
|
});
|
|
});
|
|
|
|
// ── VOUCHER REDEMPTION & SCANNER TIMER ──
|
|
function triggerVoucherRedemption(benefit) {
|
|
activeVoucher = benefit;
|
|
|
|
// Populate DOM elements
|
|
voucherIcon.textContent = benefit.icon || '🎁';
|
|
voucherTitle.textContent = benefit.title;
|
|
voucherPartner.textContent = benefit.partner || 'Estabelecimento';
|
|
|
|
// Generate dynamic unique token
|
|
const randToken = 'C67-' + Math.random().toString(36).substring(2, 7).toUpperCase();
|
|
voucherToken.textContent = randToken;
|
|
|
|
// Update QR Code Source with parameters
|
|
qrCodeImg.src = `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${randToken}&color=0f172a`;
|
|
|
|
// Switch to redeem container view
|
|
redeemCardEmpty.classList.add('hidden');
|
|
redeemCardActive.classList.remove('hidden');
|
|
|
|
// Switch view tab
|
|
switchTab('redeem');
|
|
|
|
// Start 5 minutes expiration timer
|
|
startVoucherTimer();
|
|
}
|
|
|
|
function startVoucherTimer() {
|
|
if (countdownInterval) clearInterval(countdownInterval);
|
|
|
|
timerRemainingSeconds = timerTotalSeconds;
|
|
updateTimerDisplay();
|
|
|
|
countdownInterval = setInterval(() => {
|
|
timerRemainingSeconds--;
|
|
updateTimerDisplay();
|
|
|
|
if (timerRemainingSeconds <= 0) {
|
|
handleVoucherExpiration();
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function updateTimerDisplay() {
|
|
const min = Math.floor(timerRemainingSeconds / 60);
|
|
const sec = timerRemainingSeconds % 60;
|
|
|
|
timerText.textContent = `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
|
|
|
|
// Progress bar percentage shrink
|
|
const pct = (timerRemainingSeconds / timerTotalSeconds) * 100;
|
|
timerBar.style.width = `${pct}%`;
|
|
|
|
// Danger colors for low time
|
|
if (timerRemainingSeconds < 60) {
|
|
timerBar.className = 'h-full bg-gradient-to-r from-red-500 to-rose-500 transition-all duration-1000';
|
|
timerText.className = 'text-red-400 font-mono';
|
|
} else {
|
|
timerBar.className = 'h-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all duration-1000';
|
|
timerText.className = 'text-indigo-400 font-mono';
|
|
}
|
|
}
|
|
|
|
function handleVoucherExpiration() {
|
|
clearInterval(countdownInterval);
|
|
alert('Este token de resgate expirou por limite de tempo de segurança. Por favor, gere um novo voucher.');
|
|
cancelVoucher();
|
|
}
|
|
|
|
function cancelVoucher() {
|
|
if (countdownInterval) clearInterval(countdownInterval);
|
|
activeVoucher = null;
|
|
redeemCardActive.classList.add('hidden');
|
|
redeemCardEmpty.classList.remove('hidden');
|
|
}
|
|
|
|
btnCancel.addEventListener('click', cancelVoucher);
|
|
|
|
// ── VOUCHER VALIDATION SIMULATOR (API CONSUME) ──
|
|
btnValidate.addEventListener('click', async () => {
|
|
if (!activeVoucher) return;
|
|
|
|
btnValidate.disabled = true;
|
|
btnValidate.textContent = 'VALIDANDO...';
|
|
|
|
try {
|
|
// Dynamic validation call to backend API
|
|
const response = await fetch('/api/benefits/validate', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tokenId: voucherToken.textContent,
|
|
benefitId: activeVoucher.id
|
|
})
|
|
});
|
|
|
|
// Simulated UI validation popup for high wow-factor and offline resilience
|
|
setTimeout(() => {
|
|
alert(`✅ SUCESSO!
|
|
O Voucher "${activeVoucher.title}" foi validado com sucesso e consumido pelo parceiro!
|
|
Token: ${voucherToken.textContent}
|
|
Estabelecimento: ${activeVoucher.partner || 'Clube67'}`);
|
|
|
|
// Update analytics counters to reflect live usage!
|
|
analyticsData.redemptions++;
|
|
analyticsData.savings += 45; // average discount saving value
|
|
|
|
cancelVoucher();
|
|
btnValidate.disabled = false;
|
|
btnValidate.textContent = 'Simular Validação (Estabelecimento)';
|
|
|
|
// Automatically redirect to Analytics to see the metric update live!
|
|
switchTab('analytics');
|
|
}, 1200);
|
|
|
|
} catch (err) {
|
|
console.error('Validation API fail:', err);
|
|
btnValidate.disabled = false;
|
|
btnValidate.textContent = 'Simular Validação (Estabelecimento)';
|
|
}
|
|
});
|
|
|
|
// ── ANALYTICS CHARTS & COUNTERS ──
|
|
function loadAnalyticsCharts() {
|
|
// Load live dynamic counters
|
|
statMembers.textContent = analyticsData.members.toLocaleString();
|
|
statRedemptions.textContent = analyticsData.redemptions.toLocaleString();
|
|
statSavings.textContent = `R$ ${analyticsData.savings.toLocaleString('pt-BR')}`;
|
|
|
|
// Load SVG gauge radial chart progress
|
|
const radius = 64;
|
|
const circumference = 2 * Math.PI * radius; // 402.12
|
|
const pct = analyticsData.goalPercentage;
|
|
const offset = circumference - (pct / 100) * circumference;
|
|
|
|
radialProgressBar.style.strokeDasharray = `${circumference}`;
|
|
radialProgressBar.style.strokeDashoffset = `${offset}`;
|
|
radialPercentage.textContent = `${pct}%`;
|
|
|
|
// Load vertical bar charts heights
|
|
setTimeout(() => {
|
|
barSeg.style.height = `${analyticsData.weeklyDistribution.seg}%`;
|
|
barTer.style.height = `${analyticsData.weeklyDistribution.ter}%`;
|
|
barQua.style.height = `${analyticsData.weeklyDistribution.qua}%`;
|
|
barQui.style.height = `${analyticsData.weeklyDistribution.qui}%`;
|
|
barSex.style.height = `${analyticsData.weeklyDistribution.sex}%`;
|
|
barSab.style.height = `${analyticsData.weeklyDistribution.sab}%`;
|
|
barDom.style.height = `${analyticsData.weeklyDistribution.dom}%`;
|
|
}, 100);
|
|
}
|
|
|
|
// Initialize Catalog Fetching
|
|
loadBenefits();
|
|
});
|