feat: implement advanced reward management and wheel customization

- Add customizable background color
- Add wheel animations and sound effects
- Add reward description field and winner modal
- Change font to Dana
- Add configurable reward probabilities
- Support reward types (Empty, Discount Code, LiveCoin)
- Replace reward title with reward type selector
- Allow wheel page title customization from admin panel
This commit is contained in:
2026-06-10 14:55:07 +03:30
parent b0ff339525
commit ec86cdc528
26 changed files with 1166 additions and 533 deletions

View File

@@ -1,103 +1,158 @@
import {
adminGetItems,
createItem,
deleteItem,
updateItem // 👈 باید اینو هم به api.js اضافه کنی
} from './api.js';
import { adminGetItems, createItem, deleteItem, updateItem, saveBgColor, getBgColor, saveTitleSettings, getTitleSettings } from './api.js';
const tableBody = document.getElementById('items-table-body');
const titleInput = document.getElementById('title-input');
const valueInput = document.getElementById('value-input');
const addBtn = document.getElementById('add-btn');
const editModal = document.getElementById('edit-modal');
const saveEdit = document.getElementById('save-edit');
const cancelEdit = document.getElementById('cancel-edit');
const colorPicker = document.getElementById('bg-color-picker');
const applyColor = document.getElementById('apply-color');
const previewBox = document.getElementById('color-preview-box');
const presets = document.querySelectorAll('.preset');
let editId = null; // 👈 حالت ویرایش
// -------------------- Render --------------------
async function renderItems() {
const items = await adminGetItems();
tableBody.innerHTML = '';
items.forEach((item, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${index + 1}</td>
<td>${item.title}</td>
<td>${item.value}</td>
<td class="actions">
<button
class="edit-btn"
onclick="editItem(${item.id}, '${item.title}', ${item.value})"
>
✏️ ویرایش
</button>
<button
class="delete-btn"
onclick="removeItem(${item.id})"
>
🗑 حذف
</button>
</td>
`;
tableBody.appendChild(row);
// ═══ رنگ پس‌زمینه ═══
function initColor() {
const saved = getBgColor();
colorPicker.value = saved;
previewBox.style.background = saved;
presets.forEach(p => {
if (p.dataset.color === saved) p.classList.add('active');
});
}
// -------------------- Delete --------------------
presets.forEach(p => {
p.addEventListener('click', () => {
presets.forEach(x => x.classList.remove('active'));
p.classList.add('active');
colorPicker.value = p.dataset.color;
previewBox.style.background = p.dataset.color;
});
});
colorPicker.addEventListener('input', () => {
previewBox.style.background = colorPicker.value;
presets.forEach(x => x.classList.remove('active'));
});
applyColor.addEventListener('click', () => {
saveBgColor(colorPicker.value);
applyColor.textContent = '✓ ذخیره شد';
setTimeout(() => applyColor.textContent = 'اعمال', 2000);
});
// ═══ عنوان صفحه گردونه ═══
function initTitle() {
const settings = getTitleSettings();
document.getElementById('title-text-input').value = settings.text;
document.getElementById('title-color-picker').value = settings.color;
document.getElementById('title-preview').textContent = settings.text;
document.getElementById('title-preview').style.color = settings.color;
}
document.getElementById('title-text-input').addEventListener('input', (e) => {
document.getElementById('title-preview').textContent = e.target.value || 'گردونه شانس';
});
document.getElementById('title-color-picker').addEventListener('input', (e) => {
document.getElementById('title-preview').style.color = e.target.value;
});
document.getElementById('apply-title').addEventListener('click', () => {
const text = document.getElementById('title-text-input').value.trim() || 'گردونه شانس';
const color = document.getElementById('title-color-picker').value;
saveTitleSettings(text, color);
document.getElementById('apply-title').textContent = '✓ ذخیره شد';
setTimeout(() => document.getElementById('apply-title').textContent = 'اعمال', 2000);
});
// ═══ badge نوع جایزه ═══
function prizeBadge(type) {
const map = {
'پوچ': ['badge-pooch', 'پوچ'],
'کد تخفیف': ['badge-discount', 'کد تخفیف'],
'لایوکوین': ['badge-coin', 'لایوکوین (LC)'],
};
const [cls, label] = map[type] || ['badge-pooch', type];
return `<span class="badge ${cls}">${label}</span>`;
}
// ═══ رندر جدول ═══
async function renderItems() {
const items = await adminGetItems();
tableBody.innerHTML = '';
items.forEach((item, idx) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${idx + 1}</td>
<td>${prizeBadge(item.prize_type)}</td>
<td>${item.title}</td>
<td>${item.prize}</td>
<td>${item.probability}</td>
<td>${item.description || '—'}</td>
<td>
<button class="edit-btn" onclick="openEdit(${item.id})">✏️ ویرایش</button>
<button class="delete-btn" onclick="removeItem(${item.id})">🗑 حذف</button>
</td>
`;
tableBody.appendChild(row);
});
window._items = items;
}
// ═══ افزودن آیتم ═══
addBtn.onclick = async () => {
const data = {
prize_type: document.getElementById('new-prize-type').value,
title: document.getElementById('new-title').value.trim(),
prize: document.getElementById('new-prize').value.trim(),
probability: parseFloat(document.getElementById('new-probability').value),
description: document.getElementById('new-description').value.trim(),
};
if (!data.title || !data.prize) return alert('عنوان و مقدار جایزه رو پر کن');
await createItem(data);
document.getElementById('new-title').value = '';
document.getElementById('new-prize').value = '';
document.getElementById('new-description').value = '';
document.getElementById('new-probability').value = '1';
renderItems();
};
// ═══ حذف ═══
window.removeItem = async (id) => {
if (!confirm('آیا مطمئنی؟')) return;
await deleteItem(id);
renderItems();
};
// -------------------- Edit --------------------
window.editItem = (id, title, value) => {
titleInput.value = title;
valueInput.value = value;
editId = id;
addBtn.textContent = 'آپدیت ✏️';
// ═══ ویرایش ═══
window.openEdit = (id) => {
const item = window._items.find(i => i.id === id);
if (!item) return;
document.getElementById('edit-id').value = item.id;
document.getElementById('edit-prize-type').value = item.prize_type;
document.getElementById('edit-title').value = item.title;
document.getElementById('edit-prize').value = item.prize;
document.getElementById('edit-probability').value = item.probability;
document.getElementById('edit-description').value = item.description || '';
editModal.classList.remove('hidden');
};
// -------------------- Add / Update --------------------
addBtn.onclick = async () => {
const title = titleInput.value.trim();
const value = valueInput.value.trim();
if (!title || !value) {
alert('همه فیلدها را پر کن');
return;
}
// حالت ویرایش
if (editId) {
await updateItem(editId, {
title,
value: parseInt(value)
});
editId = null;
addBtn.textContent = 'افزودن';
}
// حالت افزودن
else {
await createItem({
title,
value: parseInt(value)
});
}
titleInput.value = '';
valueInput.value = '';
cancelEdit.onclick = () => editModal.classList.add('hidden');
saveEdit.onclick = async () => {
const id = document.getElementById('edit-id').value;
const data = {
prize_type: document.getElementById('edit-prize-type').value,
title: document.getElementById('edit-title').value.trim(),
prize: document.getElementById('edit-prize').value.trim(),
probability: parseFloat(document.getElementById('edit-probability').value),
description: document.getElementById('edit-description').value.trim(),
};
await updateItem(id, data);
editModal.classList.add('hidden');
renderItems();
};
// -------------------- Init --------------------
initColor();
initTitle();
renderItems();