Sasha commited on
Commit
91f86ea
·
1 Parent(s): 80fc693

Optimize KPI cards to request overall stats summary from /api/stats/summary and display true totals.

Browse files
Files changed (3) hide show
  1. client/src/App.jsx +13 -14
  2. server/db.js +84 -0
  3. server/server.js +13 -0
client/src/App.jsx CHANGED
@@ -300,6 +300,7 @@ export default function App() {
300
  const [activityData, setActivityData] = useState([]);
301
  const [modActions, setModActions] = useState([]);
302
  const [modSummary, setModSummary] = useState([]);
 
303
 
304
  // RPG Mod Profiles
305
  const [modProfiles, setModProfiles] = useState([]);
@@ -520,6 +521,17 @@ export default function App() {
520
  const chattersData = await chattersRes.json();
521
  setChatters(Array.isArray(chattersData) ? chattersData : []);
522
 
 
 
 
 
 
 
 
 
 
 
 
523
  // 2. Spoken voice words
524
  const voiceRes = await fetch(getUrl('/api/stats/words', { type: 'voice', limit: 60 }));
525
  const voiceData = await voiceRes.json();
@@ -613,20 +625,7 @@ export default function App() {
613
  });
614
  };
615
 
616
- // KPI Metrics Calculation
617
- const totalMessages = chatters.reduce((acc, curr) => acc + curr.message_count, 0);
618
- const totalUniqueChatters = chatters.length;
619
- const topChatter = chatters[0]?.display_name || '—';
620
- const totalVoiceWords = voiceWords.reduce((acc, curr) => acc + curr.word_count, 0);
621
- const totalModActionsCount = modActions.length;
622
-
623
- const statsSummary = {
624
- messages: totalMessages,
625
- uniqueChatters: totalUniqueChatters,
626
- mostActiveChatter: topChatter,
627
- voiceWordsCount: totalVoiceWords,
628
- modActionsCount: totalModActionsCount
629
- };
630
 
631
  // Chart Setup
632
  const chartLabels = activityData.map(d => {
 
300
  const [activityData, setActivityData] = useState([]);
301
  const [modActions, setModActions] = useState([]);
302
  const [modSummary, setModSummary] = useState([]);
303
+ const [statsSummary, setStatsSummary] = useState({ messages: 0, uniqueChatters: 0, mostActiveChatter: '—', voiceWordsCount: 0, modActionsCount: 0 });
304
 
305
  // RPG Mod Profiles
306
  const [modProfiles, setModProfiles] = useState([]);
 
521
  const chattersData = await chattersRes.json();
522
  setChatters(Array.isArray(chattersData) ? chattersData : []);
523
 
524
+ // 1.5. Overall stream stats summary (true totals)
525
+ try {
526
+ const statsSummaryRes = await fetch(getUrl('/api/stats/summary'));
527
+ const statsSummaryData = await statsSummaryRes.json();
528
+ if (statsSummaryData && !statsSummaryData.error) {
529
+ setStatsSummary(statsSummaryData);
530
+ }
531
+ } catch (err) {
532
+ console.error('Error fetching stream stats summary:', err);
533
+ }
534
+
535
  // 2. Spoken voice words
536
  const voiceRes = await fetch(getUrl('/api/stats/words', { type: 'voice', limit: 60 }));
537
  const voiceData = await voiceRes.json();
 
625
  });
626
  };
627
 
628
+
 
 
 
 
 
 
 
 
 
 
 
 
 
629
 
630
  // Chart Setup
631
  const chartLabels = activityData.map(d => {
server/db.js CHANGED
@@ -991,6 +991,90 @@ export async function getTopChatters(streamId = null, limit = 50) {
991
  }
992
  }
993
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
  /**
995
  * Get top spoken words
996
  */
 
991
  }
992
  }
993
 
994
+ /**
995
+ * Get overall summary statistics (true total metrics)
996
+ */
997
+ export async function getStatsSummary(streamId = null) {
998
+ if (dbMode === 'sqlite') {
999
+ let msgCount, uniqueChatters, voiceCount, topChatter;
1000
+
1001
+ if (streamId) {
1002
+ msgCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM messages WHERE stream_id = ?').get(streamId).count;
1003
+ uniqueChatters = sqliteDb.prepare('SELECT COUNT(DISTINCT username) as count FROM messages WHERE stream_id = ?').get(streamId).count;
1004
+ voiceCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM voice_words WHERE stream_id = ?').get(streamId).count;
1005
+ const row = sqliteDb.prepare('SELECT display_name FROM stream_viewers WHERE stream_id = ? AND has_chatted = 1 ORDER BY (SELECT COUNT(*) FROM messages m WHERE m.stream_id = ? AND m.username = stream_viewers.username) DESC LIMIT 1').get(streamId, streamId);
1006
+ topChatter = row ? row.display_name : '—';
1007
+ } else {
1008
+ msgCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM messages').get().count;
1009
+ uniqueChatters = sqliteDb.prepare('SELECT COUNT(DISTINCT username) as count FROM messages').get().count;
1010
+ voiceCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM voice_words').get().count;
1011
+ const row = sqliteDb.prepare('SELECT username FROM messages GROUP BY username ORDER BY COUNT(*) DESC LIMIT 1').get();
1012
+ topChatter = row ? row.username : '—';
1013
+ }
1014
+
1015
+ return {
1016
+ messages: msgCount,
1017
+ uniqueChatters: uniqueChatters,
1018
+ mostActiveChatter: topChatter,
1019
+ voiceWordsCount: voiceCount
1020
+ };
1021
+ } else {
1022
+ // Supabase Mode
1023
+ let msgCountQuery = supabase.from('messages').select('*', { count: 'exact', head: true });
1024
+ let voiceCountQuery = supabase.from('voice_words').select('*', { count: 'exact', head: true });
1025
+
1026
+ if (streamId) {
1027
+ msgCountQuery = msgCountQuery.eq('stream_id', streamId);
1028
+ voiceCountQuery = voiceCountQuery.eq('stream_id', streamId);
1029
+ }
1030
+
1031
+ const { count: msgCount } = await msgCountQuery;
1032
+ const { count: voiceCount } = await voiceCountQuery;
1033
+
1034
+ // Unique chatters count
1035
+ let uniqueChatters = 0;
1036
+ if (streamId) {
1037
+ const { count } = await supabase
1038
+ .from('stream_chatter_stats')
1039
+ .select('*', { count: 'exact', head: true })
1040
+ .eq('stream_id', streamId);
1041
+ uniqueChatters = count || 0;
1042
+ } else {
1043
+ const { count } = await supabase
1044
+ .from('global_chatter_stats')
1045
+ .select('*', { count: 'exact', head: true });
1046
+ uniqueChatters = count || 0;
1047
+ }
1048
+
1049
+ // Top active chatter
1050
+ let topChatter = '—';
1051
+ if (streamId) {
1052
+ const { data } = await supabase
1053
+ .from('stream_chatter_stats')
1054
+ .select('display_name')
1055
+ .eq('stream_id', streamId)
1056
+ .order('message_count', { ascending: false })
1057
+ .limit(1);
1058
+ if (data && data.length > 0) topChatter = data[0].display_name;
1059
+ } else {
1060
+ const { data } = await supabase
1061
+ .from('global_chatter_stats')
1062
+ .select('username')
1063
+ .order('message_count', { ascending: false })
1064
+ .limit(1);
1065
+ if (data && data.length > 0) topChatter = data[0].username;
1066
+ }
1067
+
1068
+ return {
1069
+ messages: msgCount || 0,
1070
+ uniqueChatters: uniqueChatters,
1071
+ mostActiveChatter: topChatter,
1072
+ voiceWordsCount: voiceCount || 0
1073
+ };
1074
+ }
1075
+ }
1076
+
1077
+
1078
  /**
1079
  * Get top spoken words
1080
  */
server/server.js CHANGED
@@ -23,6 +23,7 @@ import {
23
  getModActionsSummaryRaw,
24
  syncActiveStream,
25
  getOrStartActiveStream,
 
26
  getModeratorProfilesData,
27
  getActiveStream,
28
  getPendingBackfillStreams,
@@ -519,6 +520,18 @@ app.get('/api/stats/chatters', cacheMiddleware(), async (req, res) => {
519
  res.json(chatters);
520
  });
521
 
 
 
 
 
 
 
 
 
 
 
 
 
522
  // Stop words arrays (Russian and English) to filter out common articles, prepositions
523
  const STOP_WORDS = new Set([
524
  // Russian
 
23
  getModActionsSummaryRaw,
24
  syncActiveStream,
25
  getOrStartActiveStream,
26
+ getStatsSummary,
27
  getModeratorProfilesData,
28
  getActiveStream,
29
  getPendingBackfillStreams,
 
520
  res.json(chatters);
521
  });
522
 
523
+ // Get true total summary statistics
524
+ app.get('/api/stats/summary', cacheMiddleware(), async (req, res) => {
525
+ const streamId = req.query.stream_id ? parseInt(req.query.stream_id) : null;
526
+ try {
527
+ const summary = await getStatsSummary(streamId);
528
+ res.json(summary);
529
+ } catch (err) {
530
+ console.error('[API Error] /api/stats/summary:', err);
531
+ res.status(500).json({ error: 'Internal Server Error' });
532
+ }
533
+ });
534
+
535
  // Stop words arrays (Russian and English) to filter out common articles, prepositions
536
  const STOP_WORDS = new Set([
537
  // Russian