1+ import { Delete , Refresh , YouTube } from "@mui/icons-material" ;
12import {
23 FormControl ,
34 InputLabel ,
@@ -8,36 +9,102 @@ import {
89 ListItemText ,
910 Typography ,
1011 Button ,
12+ Divider ,
13+ IconButton ,
14+ Tooltip ,
15+ CircularProgress ,
16+ ListItemIcon ,
17+ Box ,
1118} from "@mui/material" ;
12- import { isWithinInterval } from "date-fns" ;
13- import { useState } from "react" ;
14- import { useRunEventStreamSetter } from "src/data/admin-api/event-streams" ;
19+ import { Fragment , useMemo , useState } from "react" ;
20+ import { useLivestreamDelete , useRunEventStreamSetter } from "src/data/admin-api/event-streams" ;
21+ import {
22+ EventStream ,
23+ useGetEventStreamsFromEventIds ,
24+ } from "src/data/supabase/av-tools" ;
1525import { useGetEventsForSeason } from "src/data/supabase/events" ;
1626import { useGetSeasons } from "src/data/supabase/seasons" ;
1727
1828const EventLivestreams = ( ) => {
29+ // Seasons
1930 const seasons = useGetSeasons ( ) ;
31+
32+ // Track Selected Season
2033 const [ seasonId , setSeasonId ] = useState < number | undefined > (
2134 seasons . data ?. [ seasons . data ?. length - 1 ] ?. id
2235 ) ;
36+
37+ // Track loading state for single event updates
38+ const [ singleLoading , setSingleLoading ] = useState < string > ( "" ) ;
39+ const [ singleDelete , setSingleDelete ] = useState < string > ( "" ) ;
40+
41+ // Events for Season
2342 const events = useGetEventsForSeason ( seasonId ?? - 1 ) ;
24- const filteredEvents = ( events . data ?? [ ] ) . filter (
25- ( ev ) =>
26- ev . truck_routes &&
27- isWithinInterval ( new Date ( ) , {
28- start : ev . start_time ,
29- end : ev . end_time ,
30- } )
43+
44+ // Filter to events with routes that are current or upcoming
45+ const filteredEvents = useMemo (
46+ ( ) =>
47+ ( events . data ?? [ ] ) . filter (
48+ ( ev ) =>
49+ ev . truck_routes &&
50+ ( ev . start_time >= new Date ( ) || ev . end_time >= new Date ( ) )
51+ ) ,
52+ [ events . data ]
53+ ) ;
54+
55+ // Get existing streams for these events
56+ const eventStreams = useGetEventStreamsFromEventIds (
57+ filteredEvents . map ( ( ev ) => ev . id )
3158 ) ;
3259
60+ // Map of eventId to streams
61+ const streamMap = useMemo ( ( ) => {
62+ const map : { [ eventId : string ] : EventStream [ ] } = { } ;
63+ if ( eventStreams . data ) {
64+ for ( const stream of eventStreams . data ) {
65+ if ( ! map [ stream . event_id ] ) map [ stream . event_id ] = [ ] ;
66+ map [ stream . event_id ] . push ( stream ) ;
67+ }
68+ }
69+ console . log ( map ) ;
70+ return map ;
71+ } , [ eventStreams . data ] ) ;
72+
3373 const setEvents = useRunEventStreamSetter ( ) ;
74+ const deleteStream = useLivestreamDelete ( ) ;
3475
3576 const runSetEvents = ( ) => {
36- setEvents . mutate ( {
77+ setEvents . mutateAsync ( {
3778 eventIds : filteredEvents . map ( ( ev ) => ev . id ) ,
79+ } ) . finally ( ( ) => {
80+ eventStreams . refetch ( ) ;
3881 } ) ;
3982 } ;
4083
84+ const runSingleEvent = ( eventId : string ) => {
85+ setSingleLoading ( eventId ) ;
86+ setEvents
87+ . mutateAsync ( {
88+ eventIds : [ eventId ] ,
89+ } )
90+ . finally ( ( ) => {
91+ eventStreams . refetch ( ) ;
92+ setSingleLoading ( "" ) ;
93+ } ) ;
94+ } ;
95+
96+ const runDeleteStream = ( livestreamId : string ) => {
97+ setSingleDelete ( livestreamId . toString ( ) ) ;
98+ deleteStream
99+ . mutateAsync ( {
100+ livestreamId,
101+ } )
102+ . finally ( ( ) => {
103+ eventStreams . refetch ( ) ;
104+ setSingleDelete ( "" ) ;
105+ } ) ;
106+ } ;
107+
41108 return (
42109 < >
43110 { ! seasons . isPending && (
@@ -69,37 +136,109 @@ const EventLivestreams = () => {
69136 { ! events . isPending && (
70137 < >
71138 < List >
72- { ( events . data ?? [ ] ) . length === 0 ? (
139+ { filteredEvents . length === 0 ? (
73140 < ListItem >
74141 < ListItemText primary = "No events for this season" />
75142 </ ListItem >
76143 ) : (
77144 filteredEvents . map ( ( ev : any ) => (
78- < ListItem key = { ev . id } divider >
79- < ListItemText
80- primary = { ev . name }
81- secondary = {
82- ( ev . start_time
83- ? new Date ( ev . start_time ) . toLocaleString ( )
84- : "" ) +
85- ( ev . location ? ` — ${ ev . location } ` : "" ) +
86- ( ev . truck_routes ? ` — ${ ev . truck_routes . name } ` : "" )
145+ < Fragment key = { ev . id } >
146+ < ListItem
147+ divider
148+ secondaryAction = {
149+ < Tooltip title = "Update or Create Livestream" >
150+ < IconButton
151+ edge = "end"
152+ aria-label = "delete"
153+ onClick = { ( ) => runSingleEvent ( ev . id ) }
154+ disabled = { singleLoading === ev . id }
155+ >
156+ { singleLoading === ev . id ? (
157+ < CircularProgress size = { 24 } />
158+ ) : (
159+ < Refresh />
160+ ) }
161+ </ IconButton >
162+ </ Tooltip >
87163 }
88- />
89- </ ListItem >
164+ >
165+ < ListItemText
166+ primary = { ev . name }
167+ secondary = {
168+ ( ev . start_time
169+ ? new Date ( ev . start_time ) . toLocaleString ( )
170+ : "" ) +
171+ ( ev . location ? ` — ${ ev . location } ` : "" ) +
172+ ( ev . truck_routes
173+ ? ` — ${ ev . truck_routes . name } `
174+ : "" ) +
175+ ( ev . truck_routes ?. streaming_config ?. Channel_Type
176+ ? ` (${ ev . truck_routes . streaming_config . Channel_Type } - ${ ev . truck_routes . streaming_config . Channel_Id } )`
177+ : "" )
178+ }
179+ />
180+ </ ListItem >
181+
182+ { streamMap [ ev . id ] && streamMap [ ev . id ] . length > 0 && (
183+ < List sx = { { pl : 4 } } >
184+ { streamMap [ ev . id ] . map ( ( stream ) => (
185+ < ListItem
186+ key = { stream . id }
187+ secondaryAction = {
188+ < Tooltip title = "Delete Livestream" >
189+ < IconButton
190+ edge = "end"
191+ aria-label = "delete"
192+ onClick = { ( ) => runDeleteStream ( stream . id ) }
193+ disabled = { singleDelete === stream . id . toString ( ) }
194+ sx = { { mr : 10 } }
195+ >
196+ { singleDelete === stream . id . toString ( ) ? (
197+ < CircularProgress size = { 24 } />
198+ ) : (
199+ < Delete />
200+ ) }
201+ </ IconButton >
202+ </ Tooltip >
203+ }
204+ >
205+ < ListItemIcon >
206+ { stream . platform . toLowerCase ( ) === "twitch" ? (
207+ < img
208+ src = "https://pngimg.com/d/twitch_PNG48.png"
209+ alt = "Twitch"
210+ style = { { width : 24 , height : 24 } }
211+ />
212+ ) : (
213+ < YouTube />
214+ ) }
215+ </ ListItemIcon >
216+ < ListItemText
217+ primary = { stream . title }
218+ secondary = { `${ stream . platform } - ${ stream . url } ` }
219+ />
220+ </ ListItem >
221+ ) ) }
222+ </ List >
223+ ) }
224+
225+ < Divider />
226+ </ Fragment >
90227 ) )
91228 ) }
92229 </ List >
93230 </ >
94231 ) }
95232
96- < Button
97- variant = "contained"
98- loading = { setEvents . isPending }
99- onClick = { runSetEvents }
100- >
101- Set Livestreams for Current, Routed Events
102- </ Button >
233+ < Box sx = { { display : 'flex' , justifyContent : 'flex-end' } } >
234+ < Button
235+ variant = "contained"
236+ loading = { setEvents . isPending }
237+ onClick = { runSetEvents }
238+ >
239+ Set Livestreams for All Routed Events
240+ </ Button >
241+ </ Box >
103242 </ >
104243 ) }
105244 </ >
0 commit comments