feat: باشگاه امتیازات کارکنان (موتور امتیاز، جدول رتبه، فروشگاه پاداش، نشانها، پنل مدیریت و گزارش ROI)
- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency - اتصال به لید، مشتری، معامله، فعالیت، روتین، پیشفاکتور، فاکتور و تیکت - صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو - انتقال فرمساز سرنخ به ابزارها Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import type { GamificationEvent, GamificationTxDto } from '@mento/shared';
|
||||
import { GAMIFICATION_EVENTS } from '@mento/shared';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
|
||||
export { apiError } from '@/lib/routines';
|
||||
|
||||
type IconName =
|
||||
| 'trophy'
|
||||
| 'user-plus'
|
||||
| 'funnel'
|
||||
| 'briefcase'
|
||||
| 'phone'
|
||||
| 'calendar'
|
||||
| 'document'
|
||||
| 'check'
|
||||
| 'refresh'
|
||||
| 'dollar'
|
||||
| 'chat'
|
||||
| 'star'
|
||||
| 'medal'
|
||||
| 'gift'
|
||||
| 'sliders'
|
||||
| 'building'
|
||||
| 'trend';
|
||||
|
||||
const EVENT_ICONS: Record<GamificationEvent, IconName> = {
|
||||
LEAD_CREATED: 'funnel',
|
||||
LEAD_STAGE_CHANGED: 'trend',
|
||||
LEAD_CONVERTED: 'user-plus',
|
||||
CUSTOMER_CREATED: 'user-plus',
|
||||
COMPANY_CREATED: 'building',
|
||||
DEAL_CREATED: 'briefcase',
|
||||
DEAL_STAGE_CHANGED: 'trend',
|
||||
DEAL_WON: 'trophy',
|
||||
DEAL_LOST: 'briefcase',
|
||||
CALL_LOGGED: 'phone',
|
||||
MEETING_CREATED: 'calendar',
|
||||
FOLLOW_UP_CREATED: 'calendar',
|
||||
NOTE_CREATED: 'document',
|
||||
ACTIVITY_CREATED: 'check',
|
||||
TASK_COMPLETED: 'check',
|
||||
ROUTINE_COMPLETED: 'refresh',
|
||||
QUOTE_CREATED: 'document',
|
||||
INVOICE_ISSUED: 'dollar',
|
||||
PAYMENT_REGISTERED: 'dollar',
|
||||
TICKET_CREATED: 'chat',
|
||||
TICKET_RESOLVED: 'chat',
|
||||
};
|
||||
|
||||
export function txIcon(tx: Pick<GamificationTxDto, 'kind' | 'event'>): IconName {
|
||||
if (tx.kind === 'bonus') return 'star';
|
||||
if (tx.kind === 'achievement') return 'medal';
|
||||
if (tx.kind === 'redeem' || tx.kind === 'refund') return 'gift';
|
||||
if (tx.kind === 'adjust') return 'sliders';
|
||||
return tx.event ? EVENT_ICONS[tx.event] : 'star';
|
||||
}
|
||||
|
||||
export function eventLabel(event: GamificationEvent | null | undefined): string {
|
||||
return event ? GAMIFICATION_EVENTS[event]?.label ?? event : '';
|
||||
}
|
||||
|
||||
/** عدد فارسی با جداکننده هزارگان */
|
||||
export function faNum(value: number | null | undefined): string {
|
||||
if (value == null || !Number.isFinite(value)) return '—';
|
||||
return toFaDigits(Math.round(value).toLocaleString('en-US'));
|
||||
}
|
||||
|
||||
export function signedFa(value: number): string {
|
||||
if (!value) return toFaDigits(0);
|
||||
return `${value > 0 ? '+' : '−'}${faNum(Math.abs(value))}`;
|
||||
}
|
||||
|
||||
/** «۵ دقیقه پیش»، «دیروز ۱۴:۲۰» … */
|
||||
export function timeAgo(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
const diff = Date.now() - d.getTime();
|
||||
const min = Math.floor(diff / 60_000);
|
||||
if (min < 1) return 'همین الان';
|
||||
if (min < 60) return `${toFaDigits(min)} دقیقه پیش`;
|
||||
const hours = Math.floor(min / 60);
|
||||
if (hours < 24) return `${toFaDigits(hours)} ساعت پیش`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 7) return `${toFaDigits(days)} روز پیش`;
|
||||
return new Intl.DateTimeFormat('fa-IR', { dateStyle: 'medium' }).format(d);
|
||||
}
|
||||
|
||||
export function formatDateTime(iso: string): string {
|
||||
try {
|
||||
return new Intl.DateTimeFormat('fa-IR', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(iso));
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
export const REWARD_ICONS = ['gift', 'star', 'trophy', 'medal', 'crown', 'calendar-sun', 'bag', 'target', 'fire'] as const;
|
||||
export const BADGE_ICONS = ['medal', 'trophy', 'crown', 'star', 'fire', 'phone', 'users', 'funnel', 'target', 'check'] as const;
|
||||
|
||||
export const ROLE_LABELS: Record<string, string> = {
|
||||
owner: 'مالک',
|
||||
admin: 'مدیر',
|
||||
sales: 'فروش',
|
||||
support: 'پشتیبانی',
|
||||
};
|
||||
@@ -136,15 +136,6 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
admin: true,
|
||||
module: 'leads',
|
||||
},
|
||||
{
|
||||
to: '/settings/lead-forms',
|
||||
title: 'فرمساز سرنخ',
|
||||
desc: 'لینک عمومی، iframe و QR برای جذب سرنخ',
|
||||
icon: 'document',
|
||||
tint: 'mint',
|
||||
admin: true,
|
||||
module: 'lead_forms',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -367,6 +358,15 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
tint: 'amber',
|
||||
module: 'attachments',
|
||||
},
|
||||
{
|
||||
to: '/settings/lead-forms',
|
||||
title: 'فرمساز سرنخ',
|
||||
desc: 'لینک عمومی، iframe و QR برای جذب سرنخ',
|
||||
icon: 'document',
|
||||
tint: 'mint',
|
||||
admin: true,
|
||||
module: 'lead_forms',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user