| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- 'use client';
- import type { CSSProperties } from 'react';
- import Link from 'next/link';
- import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
- import { ArrowUp, ArrowDown, Minus } from 'lucide-react';
- import MemberGradeBadge from './MemberGradeBadge';
- import type { RankingEntry, RankingType } from '@/types/response/ranking';
- interface Props {
- entries: RankingEntry[];
- type: RankingType;
- viewerMemberID?: number|null;
- }
- function formatAmount(amount: number): string {
- if (amount >= 100000000) {
- return `${(amount / 100000000).toFixed(1)}억`;
- }
- if (amount >= 10000) {
- return `${(amount / 10000).toFixed(1)}만`;
- }
- return amount.toLocaleString('ko-KR');
- }
- function formatDuration(sec: number): string {
- if (sec <= 0) {
- return '-';
- }
- const hours = Math.floor(sec / 3600);
- if (hours >= 10000) {
- return `${Math.floor(hours / 10000)}만h`;
- }
- if (hours >= 1000) {
- return `${(hours / 1000).toFixed(1)}K h`;
- }
- return `${hours}h`;
- }
- function RankChange({ change }: { change: number|null }) {
- if (change === null || change === undefined) {
- return <span className="ranking__change ranking__change--new">NEW</span>;
- }
- if (change > 0) {
- return (
- <span className="ranking__change ranking__change--up">
- <ArrowUp size={12} />{change}
- </span>
- );
- }
- if (change < 0) {
- return (
- <span className="ranking__change ranking__change--down">
- <ArrowDown size={12} />{Math.abs(change)}
- </span>
- );
- }
- return (
- <span className="ranking__change ranking__change--same">
- <Minus size={12} />
- </span>
- );
- }
- export default function RankingList({ entries, type, viewerMemberID }: Props)
- {
- if (entries.length === 0) {
- return <p className="ranking__empty">집계된 순위가 없습니다.</p>;
- }
- const topScore = entries[0]?.score ?? entries[0]?.moneyAmount ?? 1;
- return (
- <div className="ranking__list">
- <div className="ranking__list-header">
- <span className='text-center'>순위</span>
- <span className='text-center'>변동</span>
- <span className='ps-2'>회원</span>
- <span className='text-center'>진행도</span>
- <span className='text-center'>금액</span>
- {type === 'donor' ? null : <span className='text-center'>방송</span>}
- </div>
- {entries.map((entry) => {
- const isMine = viewerMemberID != null && viewerMemberID === entry.memberID;
- const progress = topScore > 0 ? Math.min(100, (entry.moneyAmount / topScore) * 100) : 0;
- const targetHref = entry.isAnonymous ? '#' : `/channel/${entry.memberID}`;
- return (
- <div key={`${entry.memberID}-${entry.rank}`} className={`ranking__row ${isMine ? 'ranking__row--mine' : ''}`}>
- <div className="ranking__row-rank">#{entry.rank}</div>
- <div className="ranking__row-change">
- <RankChange change={entry.rankChange} />
- </div>
- <div className="ranking__row-member">
- <Link href={targetHref} className="ranking__row-member-link">
- <Avatar className="h-8 w-8">
- {(entry.channelThumb ?? entry.memberThumb) ? <AvatarImage src={(entry.channelThumb ?? entry.memberThumb)!} alt={entry.memberName} /> : null}
- <AvatarFallback>{entry.memberName?.[0] ?? '?'}</AvatarFallback>
- </Avatar>
- <span className="ranking__row-name">
- <span className="ranking__row-name-main">
- {entry.memberName}
- <MemberGradeBadge grade={entry.memberGrade} />
- </span>
- {entry.channelName && entry.channelName !== entry.memberName ? (
- <small className="ranking__row-channel">{entry.channelName}</small>
- ) : null}
- </span>
- </Link>
- </div>
- <div className="ranking__row-progress">
- <div className="ranking__row-progress-bar" style={{ '--progress': `${progress}%` } as CSSProperties}>
- <span />
- </div>
- </div>
- <div className="ranking__row-amount">
- {formatAmount(entry.moneyAmount)}
- {type === 'donor' ? 'P' : '원'}
- </div>
- {type !== 'donor' ? (
- <div className="ranking__row-broadcast">
- <span>{formatDuration(entry.broadcastSec)}</span>
- <small>{formatAmount(entry.viewCount)} 조회</small>
- </div>
- ) : null}
- </div>
- );
- })}
- </div>
- );
- }
|