- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency - اتصال به لید، مشتری، معامله، فعالیت، روتین، پیشفاکتور، فاکتور و تیکت - صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو - انتقال فرمساز سرنخ به ابزارها Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
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: 'پشتیبانی',
|
||
};
|