aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/SubscriberTable
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/SubscriberTable')
-rw-r--r--src/components/SubscriberTable/SubscriberTable.tsx12
-rw-r--r--src/components/SubscriberTable/TwitchDataTable.tsx182
-rw-r--r--src/components/SubscriberTable/TwitchTableRow.tsx70
3 files changed, 262 insertions, 2 deletions
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<SortKey>("subscribers");
@@ -73,6 +74,13 @@ const DataTable = ({ channel_data, timestamp }: SubscriberDataTableProp) => {
<p className="text-gray-500 text-sm">
Updated Hourly. Retrieved at: {timestamp}
</p>
+ <Link href="/twitch">
+ <button
+ className="mt-4 px-4 py-2 bg-black text-white font-semibold rounded-md hover:bg-gray-800 transition-colors"
+ >
+ Looking for &#34;The Twitch Table&#34;?
+ </button>
+ </Link>
</div>
<div className="px-2 sm:px-48 py-4 sm:py-8 relative rounded-l text-left overflow-auto">
<table className="w-full text-m sm:text-xl text-black bg-white">
@@ -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<SortKey>("twitch_followers");
+ const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
+ const [indexName, setIndexName] = useState<string>("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 (
+ <>
+ <div className="sm:hidden text-center text-red-600 font-semibold my-2">
+ Limited data shown on mobile view!
+ </div>
+ <div className="text-center sm:mt-5" style={{ fontFamily: "Quantico, sans-serif" }}>
+ <h1 className="text-2xl font-bold text-gray-800">The Twitch Table</h1>
+ <p className="text-gray-500 text-sm">Updated Hourly. Retrieved at: {timestamp}</p>
+ </div>
+
+ <div className="flex justify-center mt-2 mb-4">
+ <div className="bg-gray-100 rounded-lg p-4 shadow-sm text-sm md:text-base max-w-2xl">
+ {/* Legend wrapper - stacked sections on all screen sizes */}
+ <div className="flex flex-col gap-3">
+
+ {/* Column explanations */}
+ <div>
+ <h3 className="font-bold text-gray-800 mb-1">Column Explanations</h3>
+ <div>
+ <div className="flex items-start mb-1">
+ <span className="font-medium text-gray-700 mr-2 whitespace-nowrap">SUM(YT+TTV):</span>
+ <span className="text-gray-600">Total combined followers across both platforms</span>
+ </div>
+ <div className="flex items-start">
+ <span className="font-medium text-gray-700 mr-2 whitespace-nowrap">MAX(YT,TTV):</span>
+ <span className="text-gray-600">Highest follower count between platforms</span>
+ </div>
+ </div>
+ </div>
+
+ {/* Horizontal divider */}
+ <div className="border-t border-gray-300 w-full"></div>
+
+ {/* Color key */}
+ <div>
+ <h3 className="font-bold text-gray-800 mb-1">MAX Column Color Key</h3>
+ <div>
+ <div className="flex items-center mb-1">
+ <div className="h-3 w-3 bg-red-600 rounded-full mr-2 flex-shrink-0"></div>
+ <span className="text-gray-600">YouTube subscriber count is higher</span>
+ </div>
+ <div className="flex items-center">
+ <div className="h-3 w-3 bg-purple-600 rounded-full mr-2 flex-shrink-0"></div>
+ <span className="text-gray-600">Twitch follower count is higher</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className="px-2 sm:px-48 py-4 sm:py-8 relative rounded-l text-left overflow-auto">
+ <table className="w-full text-m sm:text-xl text-black bg-white">
+ <thead
+ className="text-m sm:text-lg text-white rounded-md select-none"
+ style={{ backgroundColor: "black" }}
+ >
+ <tr>
+ <th className="py-1 px-1 sm:px-3 hidden sm:table-cell">{indexName}</th>
+ <th className="py-1 px-1 sm:px-3">CHANNEL</th>
+ <th
+ className="py-1 px-1 sm:px-3 hidden sm:table-cell cursor-pointer"
+ onClick={() => handleSort("sub_org")}
+ >
+ GROUP
+ {sortKey === "sub_org" && (
+ <span className="ml-1">{sortOrder === "asc" ? "▲" : "▼"}</span>
+ )}
+ </th>
+ <th
+ className="py-1 px-1 sm:px-3 cursor-pointer"
+ onClick={() => handleSort("subscribers")}
+ >
+ YOUTUBE SUBS
+ {sortKey === "subscribers" && (
+ <span className="ml-1">{sortOrder === "asc" ? "▲" : "▼"}</span>
+ )}
+ </th>
+ <th
+ className="py-1 px-1 sm:px-3 cursor-pointer"
+ onClick={() => handleSort("twitch_followers")}
+ >
+ TWITCH FOLLOWS
+ {sortKey === "twitch_followers" && (
+ <span className="ml-1">{sortOrder === "asc" ? "▲" : "▼"}</span>
+ )}
+ </th>
+ <th
+ className="py-1 px-1 sm:px-3 cursor-pointer"
+ onClick={() => handleSort("total_sum")}
+ >
+ SUM(YT+TTV)
+ {sortKey === "total_sum" && (
+ <span className="ml-1">{sortOrder === "asc" ? "▲" : "▼"}</span>
+ )}
+ </th>
+ <th
+ className="py-1 px-1 sm:px-3 cursor-pointer"
+ onClick={() => handleSort("max_following")}
+ >
+ MAX(YT, TTV)
+ {sortKey === "max_following" && (
+ <span className="ml-1">{sortOrder === "asc" ? "▲" : "▼"}</span>
+ )}
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ {sortedData.map((channel, index) => (
+ <ChannelRow key={index} channel={channel} index={index} />
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </>
+ );
+};
+
+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<TwitchRowProps> = ({ channel, index }) => (
+ <tr
+ key={index}
+ className="border-b hover:bg-gray-100 cursor-pointer"
+ onClick={() => (window.location.href = "/stats/" + channel.channel_name)}
+ >
+ <td className="py-3 px-1 sm:px-3 hidden sm:table-cell">{index + 1}</td>
+ <td className="py-3 px-1 sm:px-3 flex items-center">
+ <Image
+ src={channel.profile_pic}
+ alt={channel.channel_name}
+ width={50}
+ height={50}
+ className="rounded-full"
+ />
+ <span className="ml-2 hidden sm:block">{channel.channel_name}</span>
+ <span className="ml-2 sm:hidden">{
+ (() => {
+ 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 '';
+ })()
+ }</span>
+ </td>
+ <td className="py-3 px-1 sm:px-3 hidden sm:table-cell">
+ {channel.sub_org}
+ </td>
+ <td className="py-3 px-1 sm:px-3">
+ {Number(channel.subscribers).toLocaleString()}
+ </td>
+ <td className="py-3 px-1 sm:px-3">
+ {Number(channel.twitch_followers).toLocaleString()}
+ </td>
+ <td className="py-3 px-1 sm:px-3 hidden sm:table-cell">
+ {Number(channel.total_sum).toLocaleString()}
+ </td>
+ <td className="py-2 px-3 font-bold">
+ {channel.max_following !== undefined && (
+ <span
+ className={
+ channel.twitch_followers > channel.subscribers
+ ? "text-purple-500" // Twitch color
+ : channel.subscribers > channel.twitch_followers
+ ? "text-red-600" // YouTube color
+ : ""
+ }
+ >
+ {channel.max_following.toLocaleString() || "0"}
+ </span>
+ )}
+</td>
+
+ </tr>
+);
+
+export default TwitchTableRow;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage