diff --git a/apps/web/src/assets/club/bolt.png b/apps/web/src/assets/club/bolt.png new file mode 100644 index 0000000..c506b71 Binary files /dev/null and b/apps/web/src/assets/club/bolt.png differ diff --git a/apps/web/src/assets/club/chat.png b/apps/web/src/assets/club/chat.png new file mode 100644 index 0000000..2132dfc Binary files /dev/null and b/apps/web/src/assets/club/chat.png differ diff --git a/apps/web/src/assets/club/chest.png b/apps/web/src/assets/club/chest.png new file mode 100644 index 0000000..8cb0e12 Binary files /dev/null and b/apps/web/src/assets/club/chest.png differ diff --git a/apps/web/src/assets/club/coin.png b/apps/web/src/assets/club/coin.png new file mode 100644 index 0000000..568190c Binary files /dev/null and b/apps/web/src/assets/club/coin.png differ diff --git a/apps/web/src/assets/club/crown.png b/apps/web/src/assets/club/crown.png new file mode 100644 index 0000000..ec50cb3 Binary files /dev/null and b/apps/web/src/assets/club/crown.png differ diff --git a/apps/web/src/assets/club/fire.png b/apps/web/src/assets/club/fire.png new file mode 100644 index 0000000..87986c6 Binary files /dev/null and b/apps/web/src/assets/club/fire.png differ diff --git a/apps/web/src/assets/club/gift.png b/apps/web/src/assets/club/gift.png new file mode 100644 index 0000000..ef208cd Binary files /dev/null and b/apps/web/src/assets/club/gift.png differ diff --git a/apps/web/src/assets/club/headphones.png b/apps/web/src/assets/club/headphones.png new file mode 100644 index 0000000..c8344c3 Binary files /dev/null and b/apps/web/src/assets/club/headphones.png differ diff --git a/apps/web/src/assets/club/medal.png b/apps/web/src/assets/club/medal.png new file mode 100644 index 0000000..953553f Binary files /dev/null and b/apps/web/src/assets/club/medal.png differ diff --git a/apps/web/src/assets/club/phone.png b/apps/web/src/assets/club/phone.png new file mode 100644 index 0000000..e3f6eee Binary files /dev/null and b/apps/web/src/assets/club/phone.png differ diff --git a/apps/web/src/assets/club/podium.png b/apps/web/src/assets/club/podium.png new file mode 100644 index 0000000..6581cb9 Binary files /dev/null and b/apps/web/src/assets/club/podium.png differ diff --git a/apps/web/src/assets/club/star.png b/apps/web/src/assets/club/star.png new file mode 100644 index 0000000..6eae731 Binary files /dev/null and b/apps/web/src/assets/club/star.png differ diff --git a/apps/web/src/assets/club/trophy.png b/apps/web/src/assets/club/trophy.png new file mode 100644 index 0000000..f643456 Binary files /dev/null and b/apps/web/src/assets/club/trophy.png differ diff --git a/apps/web/src/assets/club/user.png b/apps/web/src/assets/club/user.png new file mode 100644 index 0000000..5f04892 Binary files /dev/null and b/apps/web/src/assets/club/user.png differ diff --git a/apps/web/src/components/gamification/ClubHome.vue b/apps/web/src/components/gamification/ClubHome.vue index c7ad88e..e20b8ca 100644 --- a/apps/web/src/components/gamification/ClubHome.vue +++ b/apps/web/src/components/gamification/ClubHome.vue @@ -8,23 +8,68 @@ import { toFaDigits } from '@/lib/jalali'; import { faNum, signedFa, timeAgo, txIcon } from '@/lib/gamification'; import ClubLeaderboard from './ClubLeaderboard.vue'; import { useClub } from './club-context'; +import medalImg from '@/assets/club/medal.png'; +import boltImg from '@/assets/club/bolt.png'; +import coinImg from '@/assets/club/coin.png'; +import giftImg from '@/assets/club/gift.png'; +import starImg from '@/assets/club/star.png'; +import trophyImg from '@/assets/club/trophy.png'; +import crownImg from '@/assets/club/crown.png'; +import chestImg from '@/assets/club/chest.png'; +import fireImg from '@/assets/club/fire.png'; const emit = defineEmits<{ go: ['store' | 'history' | 'badges' | 'teams'] }>(); const { me } = useClub(); const rewards = ref([]); -const feed = ref([]); onMounted(async () => { - const [r, f] = await Promise.allSettled([ - api.get('/gamification/rewards'), - api.get('/gamification/feed'), - ]); - if (r.status === 'fulfilled') rewards.value = r.value.data.slice(0, 3); - if (f.status === 'fulfilled') feed.value = f.value.data.slice(0, 8); + try { + const { data } = await api.get('/gamification/rewards'); + rewards.value = data.slice(0, 3); + } catch { + rewards.value = []; + } }); const level = computed(() => me.value?.level ?? null); +const recent = computed(() => (me.value?.recent ?? []).slice(0, 5)); + +const REWARD_IMAGES: Record = { + gift: giftImg, + star: starImg, + trophy: trophyImg, + medal: medalImg, + crown: crownImg, + bag: chestImg, + fire: fireImg, +}; +const REWARD_TINTS = ['#fef3c7', '#ffedd5', '#f3e8ff']; + +function rewardImage(r: GamificationRewardDto): string | null { + return REWARD_IMAGES[r.icon] ?? null; +} + +const TX_TINTS: Record = { + trophy: '#f97316', + dollar: '#f97316', + briefcase: '#f97316', + 'user-plus': '#14b8a6', + building: '#14b8a6', + phone: '#14b8a6', + funnel: '#f59e0b', + trend: '#f59e0b', + chat: '#f59e0b', + document: '#8b5cf6', + calendar: '#8b5cf6', + check: '#8b5cf6', + refresh: '#8b5cf6', +}; + +function txTint(tx: GamificationTxDto): string { + if (tx.score < 0) return '#ef4444'; + return TX_TINTS[txIcon(tx)] ?? '#6366f1'; +} /** پیام انگیزشی؛ همیشه مثبت و بدون مقایسهٔ تحقیرآمیز */ const motivation = computed(() => { @@ -33,94 +78,94 @@ const motivation = computed(() => { if (m.rank === 1 && m.periodScore > 0) { return { title: 'عالی!', text: 'تو صدرنشین این ماهی. جایگاهت را حفظ کن!' }; } + if (m.gapToNext != null && m.gapToNext > 0 && m.rank) { + return { + title: 'عالی!', + text: `فقط ${faNum(m.gapToNext)} امتیاز تا رتبهٔ ${toFaDigits(m.rank - 1)} فاصله داری.`, + }; + } if (m.periodScore > 0 && m.periodScore >= m.personalBest) { return { title: 'رکورد جدید!', text: `${faNum(m.periodScore)} امتیاز — بهترین ماه تو تا امروز.` }; } if (m.streakDays >= 2) { return { title: 'عالی!', text: `${toFaDigits(m.streakDays)} روز پیاپی همهٔ کارهای روتین را کامل کردی.` }; } - if (m.gapToNext != null && m.gapToNext > 0 && m.rank) { - return { - title: 'نزدیکی!', - text: `فقط ${faNum(m.gapToNext)} امتیاز تا رتبهٔ ${toFaDigits(m.rank - 1)} فاصله داری.`, - }; - } if (m.todayScore > 0) { return { title: 'آفرین!', text: `امروز ${faNum(m.todayScore)} امتیاز گرفتی. ادامه بده!` }; } return { title: 'شروع کن!', text: 'با ثبت تماس، پیگیری یا یک لید جدید اولین امتیاز امروز را بگیر.' }; }); -function rewardProgress(r: GamificationRewardDto) { - const coins = me.value?.coins ?? 0; - return Math.min(100, Math.round((coins / Math.max(1, r.cost)) * 100)); -} +const cheerProgress = computed(() => { + const m = me.value; + if (!m) return 0; + if (m.rank === 1 && m.periodScore > 0) return 100; + if (m.gapToNext != null && m.gapToNext > 0) { + return Math.round((m.periodScore / (m.periodScore + m.gapToNext)) * 100); + } + return m.periodScore > 0 ? 100 : 0; +}); @@ -180,7 +211,7 @@ function rewardProgress(r: GamificationRewardDto) { diff --git a/apps/web/src/components/gamification/ClubLeaderboard.vue b/apps/web/src/components/gamification/ClubLeaderboard.vue index c9383b0..723686f 100644 --- a/apps/web/src/components/gamification/ClubLeaderboard.vue +++ b/apps/web/src/components/gamification/ClubLeaderboard.vue @@ -1,14 +1,17 @@ + +

+ معاملهٔ «{{ deleteDealTitle }}» حذف شود؟ این کار قابل بازگشت نیست. +

+ +
+