page.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { fetchJson } from '@/lib/utils/server';
  2. import type { BannerActiveListResponse, BannerItem } from '@/types/response/banner/active-list';
  3. import type { LiveChannelListResponse } from '@/types/response/channel/live-list';
  4. import type { WorldIndexRow } from '@/types/worldIndex';
  5. import type { MarketQuoteRow } from '@/types/marketQuote';
  6. import { fetchWorldIndices } from '@/lib/api/worldIndices';
  7. import { fetchMarketQuotes } from '@/lib/api/marketQuotes';
  8. import type { DomesticIndexRow } from '@/types/domesticSummary';
  9. import { fetchDomesticSummary } from '@/lib/api/domesticSummary';
  10. import type { MarketMoversResponse } from '@/types/marketMover';
  11. import { fetchMarketMovers } from '@/lib/api/marketMovers';
  12. import HomeBannerCarousel from './_components/HomeBannerCarousel';
  13. import WorldMarketMap from './_components/WorldMarketMap';
  14. import DomesticSummary from './_components/DomesticSummary';
  15. import MajorQuotesGrid from './_components/MajorQuotesGrid';
  16. import MarketMovers from './_components/MarketMovers';
  17. import LiveChannelGrid from './_components/LiveChannelGrid';
  18. import LiveChannelEmpty from './_components/LiveChannelEmpty';
  19. import { FEATURE_CHANNEL } from '@/constants/features';
  20. import './_components/home.scss';
  21. const BANNER_POSITION_CODE = 'main-top';
  22. const LIVE_PAGE_SIZE = 24;
  23. async function loadActiveBanners(): Promise<BannerItem[]> {
  24. const res = await fetchJson<BannerActiveListResponse>('/api/banner/items', {
  25. method: 'POST',
  26. body: JSON.stringify({ code: BANNER_POSITION_CODE, onlyActive: true })
  27. });
  28. if (!res.success || !res.data) {
  29. return [];
  30. }
  31. return res.data.list.filter(b => b.desktopImage !== null || b.mobileImage !== null);
  32. }
  33. async function loadLiveChannels(): Promise<{ channels: LiveChannelListResponse['channels']; total: number }> {
  34. const res = await fetchJson<LiveChannelListResponse>(`/api/channel/live-list?limit=${LIVE_PAGE_SIZE}&offset=0`, { method: 'GET' });
  35. if (!res.success || !res.data) {
  36. return { channels: [], total: 0 };
  37. }
  38. return { channels: res.data.channels, total: res.data.total };
  39. }
  40. async function loadWorldIndices(): Promise<WorldIndexRow[]> {
  41. // 백엔드 미기동/데이터 없음이어도 홈은 렌더 — 지도는 데이터 있을 때만 표시
  42. const res = await fetchWorldIndices();
  43. if (!res.success || !res.data) {
  44. return [];
  45. }
  46. return res.data.list;
  47. }
  48. async function loadMarketQuotes(): Promise<MarketQuoteRow[]> {
  49. // 백엔드 미기동/데이터 없음이어도 홈은 렌더 — 그리드는 데이터 있을 때만 표시
  50. const res = await fetchMarketQuotes();
  51. if (!res.success || !res.data) {
  52. return [];
  53. }
  54. return res.data.list;
  55. }
  56. async function loadDomesticSummary(): Promise<DomesticIndexRow[]> {
  57. const res = await fetchDomesticSummary();
  58. if (!res.success || !res.data) {
  59. return [];
  60. }
  61. return res.data.list;
  62. }
  63. async function loadMarketMovers(): Promise<MarketMoversResponse|null> {
  64. // 백엔드 미기동/데이터 없음이어도 홈은 렌더 — 섹션은 데이터 있을 때만 표시
  65. const res = await fetchMarketMovers();
  66. if (!res.success || !res.data) {
  67. return null;
  68. }
  69. return res.data;
  70. }
  71. export default async function Home() {
  72. // 채널 기능 OFF: 라이브 채널 섹션 미노출이므로 조회 생략
  73. const [banners, live, worldIndices, marketQuotes, domesticSummary, marketMovers] = await Promise.all([
  74. loadActiveBanners(),
  75. FEATURE_CHANNEL ? loadLiveChannels() : Promise.resolve({ channels: [], total: 0 }),
  76. loadWorldIndices(),
  77. loadMarketQuotes(),
  78. loadDomesticSummary(),
  79. loadMarketMovers()
  80. ]);
  81. return (
  82. <div className="home">
  83. <div className="home__inner">
  84. {worldIndices.length > 0 && <WorldMarketMap rows={worldIndices} />}
  85. {domesticSummary.length > 0 && <DomesticSummary rows={domesticSummary} />}
  86. {marketMovers && <MarketMovers data={marketMovers} />}
  87. {marketQuotes.length > 0 && <MajorQuotesGrid rows={marketQuotes} />}
  88. <HomeBannerCarousel items={banners} />
  89. {FEATURE_CHANNEL && (
  90. live.channels.length === 0 ? (
  91. <LiveChannelEmpty />
  92. ) : (
  93. <LiveChannelGrid
  94. initialChannels={live.channels}
  95. initialTotal={live.total}
  96. pageSize={LIVE_PAGE_SIZE}
  97. />
  98. )
  99. )}
  100. </div>
  101. </div>
  102. );
  103. }