From 913f28e2f27830192a1c80270612d8314eed3353 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Tue, 6 May 2025 00:05:25 -0700 Subject: phase_tracker_only: implement the twitch table --- src/components/SubscriberTable/SubscriberTable.tsx | 12 +- src/components/SubscriberTable/TwitchDataTable.tsx | 182 +++++++++++++++++++++ src/components/SubscriberTable/TwitchTableRow.tsx | 70 ++++++++ src/components/TitleBar/TitleBar.tsx | 5 + src/pages/twitch/index.tsx | 65 ++++++++ 5 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 src/components/SubscriberTable/TwitchDataTable.tsx create mode 100644 src/components/SubscriberTable/TwitchTableRow.tsx create mode 100644 src/pages/twitch/index.tsx (limited to 'src') diff --git a/src/components/SubscriberTable/SubscriberTable.tsx b/src/components/SubscriberTable/SubscriberTable.tsx index c308aee..6a73f71 100644 --- a/src/components/SubscriberTable/SubscriberTable.tsx +++ b/src/components/SubscriberTable/SubscriberTable.tsx @@ -1,5 +1,6 @@ "use client"; import React, { useState } from "react"; +import Link from "next/link"; import ChannelRow from "./SubscriberTableRow"; interface ChannelDataProp { @@ -20,7 +21,7 @@ interface SubscriberDataTableProp { timestamp: string; } -type SortKey = keyof ChannelDataProp | "rank"; +type SortKey = keyof ChannelDataProp | 'rank'; const DataTable = ({ channel_data, timestamp }: SubscriberDataTableProp) => { const [sortKey, setSortKey] = useState("subscribers"); @@ -73,6 +74,13 @@ const DataTable = ({ channel_data, timestamp }: SubscriberDataTableProp) => {

Updated Hourly. Retrieved at: {timestamp}

+ + +
@@ -159,4 +167,4 @@ const DataTable = ({ channel_data, timestamp }: SubscriberDataTableProp) => { export default DataTable; export type { SubscriberDataTableProp }; -export type { ChannelDataProp }; \ No newline at end of file +export type { ChannelDataProp }; diff --git a/src/components/SubscriberTable/TwitchDataTable.tsx b/src/components/SubscriberTable/TwitchDataTable.tsx new file mode 100644 index 0000000..3c0680c --- /dev/null +++ b/src/components/SubscriberTable/TwitchDataTable.tsx @@ -0,0 +1,182 @@ +"use client"; +import React, { useState } from "react"; +import ChannelRow from "./TwitchTableRow"; + +interface TwitchChannelDataProp { + channel_name: string; + profile_pic: string; + subscribers: number; + sub_org: string; + twitch_followers: number; + total_sum: number; + max_following?: number; +} + + +interface TwitchDataTableProp { + channel_data: TwitchChannelDataProp[]; + timestamp: string; +} + +type SortKey = keyof TwitchChannelDataProp | "rank" | "max_following"; + +const TwitchDataTable = ({ channel_data, timestamp }: TwitchDataTableProp) => { + const [sortKey, setSortKey] = useState("twitch_followers"); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); + const [indexName, setIndexName] = useState("RANK"); + + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); + } else { + setSortKey(key); + setSortOrder("desc"); + } + setIndexName(key === "sub_org" ? "INDEX" : "RANK"); + }; + + const dataWithMax = channel_data.map((channel) => ({ + ...channel, + max_following: Math.max( + channel.subscribers || 0, + channel.twitch_followers || 0 + ), + })); + + const sortedData = [...dataWithMax].sort((a, b) => { + let aValue: any, bValue: any; + if (sortKey === "rank") { + aValue = dataWithMax.indexOf(a) + 1; + bValue = dataWithMax.indexOf(b) + 1; + } else { + aValue = a[sortKey]; + bValue = b[sortKey]; + } + if (typeof aValue === "string") { + return sortOrder === "asc" + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + } + return sortOrder === "asc" ? aValue - bValue : bValue - aValue; + }); + + return ( + <> +
+ Limited data shown on mobile view! +
+
+

The Twitch Table

+

Updated Hourly. Retrieved at: {timestamp}

+
+ +
+
+ {/* Legend wrapper - stacked sections on all screen sizes */} +
+ + {/* Column explanations */} +
+

Column Explanations

+
+
+ SUM(YT+TTV): + Total combined followers across both platforms +
+
+ MAX(YT,TTV): + Highest follower count between platforms +
+
+
+ + {/* Horizontal divider */} +
+ + {/* Color key */} +
+

MAX Column Color Key

+
+
+
+ YouTube subscriber count is higher +
+
+
+ Twitch follower count is higher +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + {sortedData.map((channel, index) => ( + + ))} + +
{indexName}CHANNEL handleSort("sub_org")} + > + GROUP + {sortKey === "sub_org" && ( + {sortOrder === "asc" ? "▲" : "▼"} + )} + handleSort("subscribers")} + > + YOUTUBE SUBS + {sortKey === "subscribers" && ( + {sortOrder === "asc" ? "▲" : "▼"} + )} + handleSort("twitch_followers")} + > + TWITCH FOLLOWS + {sortKey === "twitch_followers" && ( + {sortOrder === "asc" ? "▲" : "▼"} + )} + handleSort("total_sum")} + > + SUM(YT+TTV) + {sortKey === "total_sum" && ( + {sortOrder === "asc" ? "▲" : "▼"} + )} + handleSort("max_following")} + > + MAX(YT, TTV) + {sortKey === "max_following" && ( + {sortOrder === "asc" ? "▲" : "▼"} + )} +
+
+ + ); +}; + +export default TwitchDataTable; +export type { TwitchDataTableProp, TwitchChannelDataProp }; diff --git a/src/components/SubscriberTable/TwitchTableRow.tsx b/src/components/SubscriberTable/TwitchTableRow.tsx new file mode 100644 index 0000000..b4aae9c --- /dev/null +++ b/src/components/SubscriberTable/TwitchTableRow.tsx @@ -0,0 +1,70 @@ +"use client"; +import Image from "next/image"; +import type React from "react"; +import type { TwitchChannelDataProp } from "./TwitchDataTable"; + +interface TwitchRowProps { + channel: TwitchChannelDataProp; + index: number; +} + +const TwitchTableRow: React.FC = ({ channel, index }) => ( + (window.location.href = "/stats/" + channel.channel_name)} + > + {index + 1} + + {channel.channel_name} + {channel.channel_name} + { + (() => { + const words = channel.channel_name.split(' '); + if (words.length >= 2) { + return `${words[0][0]}.${words[1][0]}`; + } else if (words.length === 1) { + return `${words[0][0]}.`; + } + return ''; + })() + } + + + {channel.sub_org} + + + {Number(channel.subscribers).toLocaleString()} + + + {Number(channel.twitch_followers).toLocaleString()} + + + {Number(channel.total_sum).toLocaleString()} + + + {channel.max_following !== undefined && ( + channel.subscribers + ? "text-purple-500" // Twitch color + : channel.subscribers > channel.twitch_followers + ? "text-red-600" // YouTube color + : "" + } + > + {channel.max_following.toLocaleString() || "0"} + + )} + + + +); + +export default TwitchTableRow; diff --git a/src/components/TitleBar/TitleBar.tsx b/src/components/TitleBar/TitleBar.tsx index af34446..f345f24 100644 --- a/src/components/TitleBar/TitleBar.tsx +++ b/src/components/TitleBar/TitleBar.tsx @@ -134,6 +134,11 @@ const TitleBar: React.FC = ({ Home + +
  • + Twitch Table +
  • +
  • About diff --git a/src/pages/twitch/index.tsx b/src/pages/twitch/index.tsx new file mode 100644 index 0000000..43d88ed --- /dev/null +++ b/src/pages/twitch/index.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from "react"; +import TwitchDataTable, { + type TwitchDataTableProp, +} from "../../components/SubscriberTable/TwitchDataTable"; +import TitleBar from "../../components/TitleBar/TitleBar"; +import Announcement from "../../components/Announcement"; +import "../../app/globals.css"; + +function TwitchPage() { + const [twitchData, setTwitchData] = useState(null); + const [error, setError] = useState(null); + + const announcementText = process.env.NEXT_PUBLIC_ANNOUNCEMENT; + + useEffect(() => { + async function fetchTwitchData() { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL_TESTING; + const endpoint = "/api/twitch"; + const headers = { + "Cache-Control": "no-cache", + }; + const cacheOption = "no-cache"; + + const response = await fetch(`${apiUrl}${endpoint}`, { + headers: headers, + cache: cacheOption, + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const data = await response.json(); + setTwitchData(data); + } catch (err) { + setError(err instanceof Error ? err.message : "An error occurred"); + } + } + + fetchTwitchData(); + }, []); + + return ( + <> + + {announcementText && ( + + )} + {error ? ( +
    Error: {error}
    + ) : twitchData ? ( + + ) : ( +
    Loading...
    + )} + + ); +} + +export default TwitchPage; -- cgit v1.2.3