RankingView.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use client';
  2. import { useCallback, useEffect, useMemo, useState } from 'react';
  3. import { useRouter, useSearchParams } from 'next/navigation';
  4. import { fetchApi } from '@/lib/utils/client';
  5. import useAuth from '@/hooks/useAuth';
  6. import { useMemberContext } from '@/contexts/memberProvider';
  7. import Loading from '@/app/component/Loading';
  8. import RankingTabs from './RankingTabs';
  9. import PeriodFilter from './PeriodFilter';
  10. import CategoryFilter from './CategoryFilter';
  11. import Podium from './Podium';
  12. import RankingList from './RankingList';
  13. import MyRankBar from './MyRankBar';
  14. import type { RankingResponse, RankingPeriod, RankingType } from '@/types/response/ranking';
  15. interface Props {
  16. type: 'comprehensive'|'creator'|'donor';
  17. basePath: string;
  18. }
  19. export default function RankingView({ type, basePath }: Props)
  20. {
  21. const router = useRouter();
  22. const searchParams = useSearchParams();
  23. const { isAuthenticated } = useAuth();
  24. const { member } = useMemberContext();
  25. const initialPeriod = (searchParams.get('period') as RankingPeriod) || 'month';
  26. const initialCategory = searchParams.get('category') || 'all';
  27. const [period, setPeriod] = useState<RankingPeriod>(initialPeriod);
  28. const [category, setCategory] = useState<string>(initialCategory);
  29. const [data, setData] = useState<RankingResponse|null>(null);
  30. const [loading, setLoading] = useState(true);
  31. const viewerMemberID = isAuthenticated ? member?.id ?? null : null;
  32. const loadData = useCallback(() => {
  33. setLoading(true);
  34. const qs = new URLSearchParams();
  35. qs.set('type', type);
  36. qs.set('period', period);
  37. qs.set('category', category);
  38. qs.set('page', '1');
  39. qs.set('perPage', '50');
  40. fetchApi<RankingResponse>(`/api/ranking?${qs.toString()}`, { silent: true }).then((res) => {
  41. setData(res.data ?? null);
  42. }).catch(() => {
  43. setData(null);
  44. }).finally(() => {
  45. setLoading(false);
  46. });
  47. }, [type, period, category]);
  48. useEffect(() => {
  49. loadData();
  50. }, [loadData]);
  51. useEffect(() => {
  52. const qs = new URLSearchParams();
  53. if (period !== 'month') {
  54. qs.set('period', period);
  55. }
  56. if (category !== 'all') {
  57. qs.set('category', category);
  58. }
  59. const query = qs.toString();
  60. router.replace(query ? `${basePath}?${query}` : basePath, { scroll: false });
  61. }, [period, category, basePath, router]);
  62. const asRankingType: RankingType = type;
  63. const snapshotLabel = useMemo(() => {
  64. if (!data?.snapshotAt) {
  65. return null;
  66. }
  67. const d = new Date(data.snapshotAt);
  68. return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} 기준`;
  69. }, [data?.snapshotAt]);
  70. return (
  71. <div className="ranking">
  72. <header className="ranking__header">
  73. <h1>순위</h1>
  74. </header>
  75. <RankingTabs active={type} />
  76. <div className="ranking__filters">
  77. <PeriodFilter value={period} onChange={setPeriod} />
  78. {type !== 'donor' ? <CategoryFilter value={category} onChange={setCategory} /> : null}
  79. </div>
  80. {loading ? (
  81. <Loading type={2} />
  82. ) : (
  83. <>
  84. {data && data.top3.length > 0 ? (
  85. <Podium top3={data.top3} type={asRankingType} />
  86. ) : null}
  87. {snapshotLabel ? <p className="ranking__list-note">{snapshotLabel}</p> : null}
  88. <RankingList entries={data?.list ?? []} type={asRankingType} viewerMemberID={viewerMemberID} />
  89. {data?.myRank ? <MyRankBar info={data.myRank} type={asRankingType} /> : null}
  90. </>
  91. )}
  92. </div>
  93. );
  94. }