Add events history
This commit is contained in:
250
frontend/src/utils/activity.ts
Normal file
250
frontend/src/utils/activity.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import type { Activity, ActivityType, EventType } from '@/types'
|
||||
import {
|
||||
UserPlus,
|
||||
RotateCcw,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Play,
|
||||
Flag,
|
||||
Plus,
|
||||
ThumbsUp,
|
||||
ThumbsDown,
|
||||
Zap,
|
||||
ZapOff,
|
||||
ArrowLeftRight,
|
||||
RefreshCw,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
|
||||
// Relative time formatting
|
||||
export function formatRelativeTime(dateStr: string): string {
|
||||
// Backend saves time in UTC, ensure we parse it correctly
|
||||
// If the string doesn't end with 'Z', append it to indicate UTC
|
||||
const utcDateStr = dateStr.endsWith('Z') ? dateStr : dateStr + 'Z'
|
||||
const date = new Date(utcDateStr)
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffSec = Math.floor(diffMs / 1000)
|
||||
const diffMin = Math.floor(diffSec / 60)
|
||||
const diffHour = Math.floor(diffMin / 60)
|
||||
const diffDay = Math.floor(diffHour / 24)
|
||||
|
||||
if (diffSec < 60) return 'только что'
|
||||
if (diffMin < 60) {
|
||||
if (diffMin === 1) return '1 мин назад'
|
||||
if (diffMin < 5) return `${diffMin} мин назад`
|
||||
if (diffMin < 21 && diffMin > 4) return `${diffMin} мин назад`
|
||||
return `${diffMin} мин назад`
|
||||
}
|
||||
if (diffHour < 24) {
|
||||
if (diffHour === 1) return '1 час назад'
|
||||
if (diffHour < 5) return `${diffHour} часа назад`
|
||||
return `${diffHour} часов назад`
|
||||
}
|
||||
if (diffDay === 1) return 'вчера'
|
||||
if (diffDay < 7) return `${diffDay} дн назад`
|
||||
|
||||
return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
|
||||
}
|
||||
|
||||
// Activity icon mapping
|
||||
export function getActivityIcon(type: ActivityType): LucideIcon {
|
||||
const icons: Record<ActivityType, LucideIcon> = {
|
||||
join: UserPlus,
|
||||
spin: RotateCcw,
|
||||
complete: CheckCircle,
|
||||
drop: XCircle,
|
||||
start_marathon: Play,
|
||||
finish_marathon: Flag,
|
||||
add_game: Plus,
|
||||
approve_game: ThumbsUp,
|
||||
reject_game: ThumbsDown,
|
||||
event_start: Zap,
|
||||
event_end: ZapOff,
|
||||
swap: ArrowLeftRight,
|
||||
rematch: RefreshCw,
|
||||
}
|
||||
return icons[type] || Zap
|
||||
}
|
||||
|
||||
// Activity color mapping
|
||||
export function getActivityColor(type: ActivityType): string {
|
||||
const colors: Record<ActivityType, string> = {
|
||||
join: 'text-green-400',
|
||||
spin: 'text-blue-400',
|
||||
complete: 'text-green-400',
|
||||
drop: 'text-red-400',
|
||||
start_marathon: 'text-green-400',
|
||||
finish_marathon: 'text-gray-400',
|
||||
add_game: 'text-blue-400',
|
||||
approve_game: 'text-green-400',
|
||||
reject_game: 'text-red-400',
|
||||
event_start: 'text-yellow-400',
|
||||
event_end: 'text-gray-400',
|
||||
swap: 'text-purple-400',
|
||||
rematch: 'text-orange-400',
|
||||
}
|
||||
return colors[type] || 'text-gray-400'
|
||||
}
|
||||
|
||||
// Activity background for special events
|
||||
export function getActivityBgClass(type: ActivityType): string {
|
||||
if (type === 'event_start') {
|
||||
return 'bg-gradient-to-r from-yellow-900/30 to-orange-900/30 border-yellow-700/50'
|
||||
}
|
||||
if (type === 'event_end') {
|
||||
return 'bg-gray-800/50 border-gray-700/50'
|
||||
}
|
||||
return 'bg-gray-800/30 border-gray-700/30'
|
||||
}
|
||||
|
||||
// Check if activity is a special event
|
||||
export function isEventActivity(type: ActivityType): boolean {
|
||||
return type === 'event_start' || type === 'event_end'
|
||||
}
|
||||
|
||||
// Event type to Russian name mapping
|
||||
const EVENT_NAMES: Record<EventType, string> = {
|
||||
golden_hour: 'Золотой час',
|
||||
common_enemy: 'Общий враг',
|
||||
double_risk: 'Двойной риск',
|
||||
jackpot: 'Джекпот',
|
||||
swap: 'Обмен',
|
||||
rematch: 'Реванш',
|
||||
}
|
||||
|
||||
// Difficulty translation
|
||||
const DIFFICULTY_NAMES: Record<string, string> = {
|
||||
easy: 'Легкий',
|
||||
medium: 'Средний',
|
||||
hard: 'Сложный',
|
||||
}
|
||||
|
||||
interface Winner {
|
||||
nickname: string
|
||||
rank: number
|
||||
bonus_points: number
|
||||
}
|
||||
|
||||
// Format activity message
|
||||
export function formatActivityMessage(activity: Activity): { title: string; details?: string; extra?: string } {
|
||||
const data = activity.data || {}
|
||||
|
||||
switch (activity.type) {
|
||||
case 'join':
|
||||
return { title: 'присоединился к марафону' }
|
||||
|
||||
case 'spin': {
|
||||
const game = (data.game as string) || ''
|
||||
const challenge = (data.challenge as string) || ''
|
||||
const difficulty = data.difficulty ? DIFFICULTY_NAMES[data.difficulty as string] || '' : ''
|
||||
|
||||
return {
|
||||
title: 'получил задание',
|
||||
details: challenge || undefined,
|
||||
extra: [game, difficulty].filter(Boolean).join(' • ') || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
case 'complete': {
|
||||
const game = (data.game as string) || ''
|
||||
const challenge = (data.challenge as string) || ''
|
||||
const points = data.points ? `+${data.points}` : ''
|
||||
const streak = data.streak && (data.streak as number) > 1 ? `серия ${data.streak}` : ''
|
||||
const bonus = data.common_enemy_bonus ? `+${data.common_enemy_bonus} бонус` : ''
|
||||
|
||||
return {
|
||||
title: `завершил ${points}`,
|
||||
details: challenge || undefined,
|
||||
extra: [game, streak, bonus].filter(Boolean).join(' • ') || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
case 'drop': {
|
||||
const game = (data.game as string) || ''
|
||||
const challenge = (data.challenge as string) || ''
|
||||
const penalty = data.penalty ? `-${data.penalty}` : ''
|
||||
const free = data.free_drop ? '(бесплатно)' : ''
|
||||
|
||||
return {
|
||||
title: `пропустил ${penalty} ${free}`.trim(),
|
||||
details: challenge || undefined,
|
||||
extra: game || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
case 'start_marathon':
|
||||
return { title: 'Марафон начался!' }
|
||||
|
||||
case 'finish_marathon':
|
||||
return { title: 'Марафон завершён!' }
|
||||
|
||||
case 'add_game':
|
||||
return {
|
||||
title: 'добавил игру',
|
||||
details: (data.game as string) || (data.game_title as string) || undefined,
|
||||
}
|
||||
|
||||
case 'approve_game':
|
||||
return {
|
||||
title: 'одобрил игру',
|
||||
details: (data.game as string) || (data.game_title as string) || undefined,
|
||||
}
|
||||
|
||||
case 'reject_game':
|
||||
return {
|
||||
title: 'отклонил игру',
|
||||
details: (data.game as string) || (data.game_title as string) || undefined,
|
||||
}
|
||||
|
||||
case 'event_start': {
|
||||
const eventName = EVENT_NAMES[data.event_type as EventType] || (data.event_name as string) || 'Событие'
|
||||
return {
|
||||
title: 'СОБЫТИЕ НАЧАЛОСЬ',
|
||||
details: eventName,
|
||||
}
|
||||
}
|
||||
|
||||
case 'event_end': {
|
||||
const eventName = EVENT_NAMES[data.event_type as EventType] || (data.event_name as string) || 'Событие'
|
||||
const winners = data.winners as Winner[] | undefined
|
||||
|
||||
let winnersText = ''
|
||||
if (winners && winners.length > 0) {
|
||||
const medals = ['🥇', '🥈', '🥉']
|
||||
winnersText = winners
|
||||
.map((w) => `${medals[w.rank - 1] || ''} ${w.nickname} +${w.bonus_points}`)
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'Событие завершено',
|
||||
details: eventName,
|
||||
extra: winnersText || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
case 'swap': {
|
||||
const challenge = (data.challenge as string) || ''
|
||||
const withUser = (data.with_user as string) || ''
|
||||
return {
|
||||
title: 'обменялся заданиями',
|
||||
details: withUser ? `с ${withUser}` : undefined,
|
||||
extra: challenge || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
case 'rematch': {
|
||||
const game = (data.game as string) || ''
|
||||
const challenge = (data.challenge as string) || ''
|
||||
return {
|
||||
title: 'взял реванш',
|
||||
details: challenge || undefined,
|
||||
extra: game || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return { title: 'выполнил действие' }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user