Add events history

This commit is contained in:
2025-12-15 22:31:42 +07:00
parent 4239ea8516
commit 9a037cb34f
7 changed files with 801 additions and 231 deletions

View 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: 'выполнил действие' }
}
}